home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / perl5 / Immunix / SubDomain.pm < prev   
Text File  |  2009-11-03  |  219KB  |  6,690 lines

  1. # $Id: SubDomain.pm 1394 2009-03-19 18:36:40Z steve-beattie $
  2. #
  3. # ----------------------------------------------------------------------
  4. #    Copyright (c) 2006 Novell, Inc. All Rights Reserved.
  5. #
  6. #    This program is free software; you can redistribute it and/or
  7. #    modify it under the terms of version 2 of the GNU General Public
  8. #    License as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program; if not, contact Novell, Inc.
  17. #
  18. #    To contact Novell about this file by physical or electronic mail,
  19. #    you may find current contact information at www.novell.com.
  20. # ----------------------------------------------------------------------
  21.  
  22. package Immunix::SubDomain;
  23.  
  24. use strict;
  25. use warnings;
  26.  
  27. use Carp;
  28. use Cwd qw(cwd realpath);
  29. use File::Basename;
  30. use File::Temp qw/ tempfile tempdir /;
  31. use Data::Dumper;
  32.  
  33. use Locale::gettext;
  34. use POSIX;
  35. use Storable qw(dclone);
  36.  
  37. use Term::ReadKey;
  38.  
  39. use Immunix::Severity;
  40. use Immunix::Repository;
  41. use Immunix::Config;
  42. use LibAppArmor;
  43.  
  44. require Exporter;
  45. our @ISA    = qw(Exporter);
  46. our @EXPORT = qw(
  47.     %sd
  48.     %qualifiers
  49.     %include
  50.     %helpers
  51.  
  52.     $filename
  53.     $profiledir
  54.     $parser
  55.     $logger
  56.     $UI_Mode
  57.     $running_under_genprof
  58.  
  59.     which
  60.     getprofilefilename
  61.     get_full_path
  62.     fatal_error
  63.     get_pager
  64.  
  65.     getprofileflags
  66.     setprofileflags
  67.     complain
  68.     enforce
  69.  
  70.     autodep
  71.     reload
  72.  
  73.     UI_GetString
  74.     UI_GetFile
  75.     UI_YesNo
  76.     UI_ShortMessage
  77.     UI_LongMessage
  78.  
  79.     UI_Important
  80.     UI_Info
  81.     UI_PromptUser
  82.     display_changes
  83.     getkey
  84.  
  85.     do_logprof_pass
  86.  
  87.     loadincludes
  88.     readprofile
  89.     readprofiles
  90.     writeprofile
  91.     serialize_profile
  92.     attach_profile_data
  93.     parse_repo_profile
  94.     activate_repo_profiles
  95.  
  96.     check_for_subdomain
  97.  
  98.     setup_yast
  99.     shutdown_yast
  100.     GetDataFromYast
  101.     SendDataToYast
  102.  
  103.     checkProfileSyntax
  104.     checkIncludeSyntax
  105.     check_qualifiers
  106.  
  107.     isSkippableFile
  108.     isSkippableDir
  109. );
  110.  
  111. our $confdir = "/etc/apparmor";
  112.  
  113. our $running_under_genprof = 0;
  114.  
  115. our $DEBUGGING;
  116.  
  117. our $unimplemented_warning = 0;
  118.  
  119. # keep track of if we're running under yast or not - default to text mode
  120. our $UI_Mode = "text";
  121.  
  122. our $sevdb;
  123.  
  124. # initialize Term::ReadLine if it's available
  125. our $term;
  126. eval {
  127.     require Term::ReadLine;
  128.     import Term::ReadLine;
  129.     $term = new Term::ReadLine 'AppArmor';
  130. };
  131.  
  132. # initialize the local poo
  133. setlocale(LC_MESSAGES, "")
  134.     unless defined(LC_MESSAGES);
  135. textdomain("apparmor-utils");
  136.  
  137. # where do we get our log messages from?
  138. our $filename;
  139.  
  140. our $cfg;
  141. our $repo_cfg;
  142.  
  143. our $parser;
  144. our $ldd;
  145. our $logger;
  146. our $profiledir;
  147. our $extraprofiledir;
  148.  
  149. # we keep track of the included profile fragments with %include
  150. my %include;
  151.  
  152. my %existing_profiles;
  153.  
  154. our $seenevents = 0;
  155.  
  156.  
  157. # these are globs that the user specifically entered.  we'll keep track of
  158. # them so that if one later matches, we'll suggest it again.
  159. our @userglobs;
  160.  
  161. ### THESE VARIABLES ARE USED WITHIN LOGPROF
  162. our %t;
  163. our %transitions;
  164. our %sd;    # we keep track of the original profiles in %sd
  165. our %original_sd;
  166. our %extras;  # inactive profiles from extras
  167.  
  168. my @log;
  169. my %pid;
  170.  
  171. my %seen;
  172. my %profilechanges;
  173. my %prelog;
  174. my %log;
  175. my %changed;
  176. my @created;
  177. my %skip;
  178. our %helpers;    # we want to preserve this one between passes
  179.  
  180. ### THESE VARIABLES ARE USED WITHIN LOGPROF
  181.  
  182. my %filelist;   # file level stuff including variables in config files
  183.  
  184. my $AA_MAY_EXEC = 1;
  185. my $AA_MAY_WRITE = 2;
  186. my $AA_MAY_READ = 4;
  187. my $AA_MAY_APPEND = 8;
  188. my $AA_MAY_LINK = 16;
  189. my $AA_MAY_LOCK = 32;
  190. my $AA_EXEC_MMAP = 64;
  191. my $AA_EXEC_UNSAFE = 128;
  192. my $AA_EXEC_INHERIT = 256;
  193. my $AA_EXEC_UNCONFINED = 512;
  194. my $AA_EXEC_PROFILE = 1024;
  195. my $AA_EXEC_CHILD = 2048;
  196. my $AA_EXEC_NT = 4096;
  197. my $AA_LINK_SUBSET = 8192;
  198.  
  199. my $AA_OTHER_SHIFT = 14;
  200. my $AA_USER_MASK = 16384 -1;
  201.  
  202. my $AA_EXEC_TYPE = $AA_MAY_EXEC | $AA_EXEC_UNSAFE | $AA_EXEC_INHERIT |
  203.             $AA_EXEC_UNCONFINED | $AA_EXEC_PROFILE | $AA_EXEC_CHILD | $AA_EXEC_NT;
  204.  
  205. my $ALL_AA_EXEC_TYPE = $AA_EXEC_TYPE;
  206.  
  207. my %MODE_HASH = (
  208.     x => $AA_MAY_EXEC,
  209.     X => $AA_MAY_EXEC,
  210.     w => $AA_MAY_WRITE,
  211.     W => $AA_MAY_WRITE,
  212.     r => $AA_MAY_READ,
  213.     R => $AA_MAY_READ,
  214.     a => $AA_MAY_APPEND,
  215.     A => $AA_MAY_APPEND,
  216.     l => $AA_MAY_LINK,
  217.     L => $AA_MAY_LINK,
  218.     k => $AA_MAY_LOCK,
  219.     K => $AA_MAY_LOCK,
  220.     m => $AA_EXEC_MMAP,
  221.     M => $AA_EXEC_MMAP,
  222. #   Unsafe => 128,
  223.     i => $AA_EXEC_INHERIT,
  224.     I => $AA_EXEC_INHERIT,
  225.     u => $AA_EXEC_UNCONFINED + $AA_EXEC_UNSAFE,        # U + Unsafe
  226.     U => $AA_EXEC_UNCONFINED,
  227.     p => $AA_EXEC_PROFILE + $AA_EXEC_UNSAFE,        # P + Unsafe
  228.     P => $AA_EXEC_PROFILE,
  229.     c => $AA_EXEC_CHILD + $AA_EXEC_UNSAFE,
  230.     C => $AA_EXEC_CHILD,
  231.     n => $AA_EXEC_NT + $AA_EXEC_UNSAFE,
  232.     N => $AA_EXEC_NT,
  233.     );
  234.  
  235. sub debug ($) {
  236.     my $message = shift;
  237.     chomp($message);
  238.  
  239.     print DEBUG "$message\n" if $DEBUGGING;
  240. }
  241.  
  242. my %arrows = ( A => "UP", B => "DOWN", C => "RIGHT", D => "LEFT" );
  243.  
  244. sub getkey {
  245.     # change to raw mode
  246.     ReadMode(4);
  247.  
  248.     my $key = ReadKey(0);
  249.  
  250.     # decode arrow key control sequences
  251.     if ($key eq "\x1B") {
  252.         $key = ReadKey(0);
  253.         if ($key eq "[") {
  254.             $key = ReadKey(0);
  255.             if ($arrows{$key}) {
  256.                 $key = $arrows{$key};
  257.             }
  258.         }
  259.     }
  260.  
  261.     # return to cooked mode
  262.     ReadMode(0);
  263.     return $key;
  264. }
  265.  
  266. BEGIN {
  267.     # set things up to log extra info if they want...
  268.     if ($ENV{LOGPROF_DEBUG}) {
  269.         $DEBUGGING = 1;
  270.         open(DEBUG, ">/var/log/apparmor/logprof_debug_$$.log");
  271.         my $oldfd = select(DEBUG);
  272.         $| = 1;
  273.         select($oldfd);
  274.     } else {
  275.         $DEBUGGING = 0;
  276.     }
  277. }
  278.  
  279. END {
  280.     $DEBUGGING && debug "Exiting...";
  281.  
  282.     # close the debug log if necessary
  283.     close(DEBUG) if $DEBUGGING;
  284. }
  285.  
  286. # returns true if the specified program contains references to LD_PRELOAD or
  287. # LD_LIBRARY_PATH to give the PX/UX code better suggestions
  288. sub check_for_LD_XXX ($) {
  289.     my $file = shift;
  290.  
  291.     return undef unless -f $file;
  292.  
  293.     # limit our checking to programs/scripts under 10k to speed things up a bit
  294.     my $size = -s $file;
  295.     return undef unless ($size && $size < 10000);
  296.  
  297.     my $found = undef;
  298.     if (open(F, $file)) {
  299.         while (<F>) {
  300.             $found = 1 if /LD_(PRELOAD|LIBRARY_PATH)/;
  301.         }
  302.         close(F);
  303.     }
  304.  
  305.     return $found;
  306. }
  307.  
  308. sub fatal_error ($) {
  309.     my $message = shift;
  310.  
  311.     my $details = "$message\n";
  312.  
  313.     if ($DEBUGGING) {
  314.  
  315.         # we'll include the stack backtrace if we're debugging...
  316.         $details = Carp::longmess($message);
  317.  
  318.         # write the error to the log
  319.         print DEBUG $details;
  320.     }
  321.  
  322.     # we'll just shoot ourselves in the head if it was one of the yast
  323.     # interface functions that ran into an error.  it gets really ugly if
  324.     # the yast frontend goes away and we try to notify the user of that
  325.     # problem by trying to send the yast frontend a pretty dialog box
  326.     my $caller = (caller(1))[3];
  327.  
  328.     exit 1 if defined($caller) && $caller =~ /::(Send|Get)Data(To|From)Yast$/;
  329.  
  330.     # tell the user what the hell happened
  331.     UI_Important($details);
  332.  
  333.     # make sure the frontend exits cleanly...
  334.     shutdown_yast();
  335.  
  336.     # die a horrible flaming death
  337.     exit 1;
  338. }
  339.  
  340. sub setup_yast {
  341.  
  342.     # set up the yast connection if we're running under yast...
  343.     if ($ENV{YAST_IS_RUNNING}) {
  344.  
  345.         # load the yast module if available.
  346.         eval { require ycp; };
  347.         unless ($@) {
  348.             import ycp;
  349.  
  350.             $UI_Mode = "yast";
  351.  
  352.             # let the frontend know that we're starting
  353.             SendDataToYast({
  354.                 type   => "initial_handshake",
  355.                 status => "backend_starting"
  356.             });
  357.  
  358.             # see if the frontend is just starting up also...
  359.             my ($ypath, $yarg) = GetDataFromYast();
  360.             unless ($yarg
  361.                 && (ref($yarg)      eq "HASH")
  362.                 && ($yarg->{type}   eq "initial_handshake")
  363.                 && ($yarg->{status} eq "frontend_starting"))
  364.             {
  365.  
  366.                 # something's broken, die a horrible, painful death
  367.                 fatal_error "Yast frontend is out of sync from backend agent.";
  368.             }
  369.             $DEBUGGING && debug "Initial handshake ok";
  370.  
  371.             # the yast connection seems to be working okay
  372.             return 1;
  373.         }
  374.  
  375.     }
  376.  
  377.     # couldn't init yast
  378.     return 0;
  379. }
  380.  
  381. sub shutdown_yast {
  382.     if ($UI_Mode eq "yast") {
  383.         SendDataToYast({ type => "final_shutdown" });
  384.         my ($ypath, $yarg) = GetDataFromYast();
  385.     }
  386. }
  387.  
  388. sub check_for_subdomain () {
  389.  
  390.     my ($support_subdomainfs, $support_securityfs);
  391.     if (open(MOUNTS, "/proc/filesystems")) {
  392.         while (<MOUNTS>) {
  393.             $support_subdomainfs = 1 if m/subdomainfs/;
  394.             $support_securityfs  = 1 if m/securityfs/;
  395.         }
  396.         close(MOUNTS);
  397.     }
  398.  
  399.     my $sd_mountpoint = "";
  400.     if (open(MOUNTS, "/proc/mounts")) {
  401.         while (<MOUNTS>) {
  402.             if ($support_subdomainfs) {
  403.                 $sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/;
  404.             } elsif ($support_securityfs) {
  405.                 if (m/^\S+\s+(\S+)\s+securityfs\s/) {
  406.                     if (-e "$1/apparmor") {
  407.                         $sd_mountpoint = "$1/apparmor";
  408.                     } elsif (-e "$1/subdomain") {
  409.                         $sd_mountpoint = "$1/subdomain";
  410.                     }
  411.                 }
  412.             }
  413.         }
  414.         close(MOUNTS);
  415.     }
  416.  
  417.     # make sure that subdomain is actually mounted there
  418.     $sd_mountpoint = undef unless -f "$sd_mountpoint/profiles";
  419.  
  420.     return $sd_mountpoint;
  421. }
  422.  
  423. sub which ($) {
  424.     my $file = shift;
  425.  
  426.     foreach my $dir (split(/:/, $ENV{PATH})) {
  427.         return "$dir/$file" if -x "$dir/$file";
  428.     }
  429.  
  430.     return undef;
  431. }
  432.  
  433. # we need to convert subdomain regexps to perl regexps
  434. sub convert_regexp ($) {
  435.     my $regexp = shift;
  436.  
  437.     # escape regexp-special characters we don't support
  438.     $regexp =~ s/(?<!\\)(\.|\+|\$)/\\$1/g;
  439.  
  440.     # * and ** globs can't collapse to match an empty string when they're
  441.     # the only part of the glob at a specific directory level, which
  442.     # complicates things a little.
  443.  
  444.     # ** globs match multiple directory levels
  445.     $regexp =~ s{(?<!\\)\*\*+}{
  446.       my ($pre, $post) = ($`, $');
  447.       if (($pre =~ /\/$/) && (!$post || $post =~ /^\//)) {
  448.         'SD_INTERNAL_MULTI_REQUIRED';
  449.       } else {
  450.         'SD_INTERNAL_MULTI_OPTIONAL';
  451.       }
  452.     }gex;
  453.  
  454.     # convert * globs to match anything at the current path level
  455.     $regexp =~ s{(?<!\\)\*}{
  456.       my ($pre, $post) = ($`, $');
  457.       if (($pre =~ /\/$/) && (!$post || $post =~ /^\//)) {
  458.         'SD_INTERNAL_SINGLE_REQUIRED';
  459.       } else {
  460.         'SD_INTERNAL_SINGLE_OPTIONAL';
  461.       }
  462.     }gex;
  463.  
  464.     # convert ? globs to match a single character at current path level
  465.     $regexp =~ s/(?<!\\)\?/[^\/]/g;
  466.  
  467.     # convert {foo,baz} to (foo|baz)
  468.     $regexp =~ y/\{\}\,/\(\)\|/ if $regexp =~ /\{.*\,.*\}/;
  469.  
  470.     # convert internal markers to their appropriate regexp equivalents
  471.     $regexp =~ s/SD_INTERNAL_SINGLE_OPTIONAL/[^\/]*/g;
  472.     $regexp =~ s/SD_INTERNAL_SINGLE_REQUIRED/[^\/]+/g;
  473.     $regexp =~ s/SD_INTERNAL_MULTI_OPTIONAL/.*/g;
  474.     $regexp =~ s/SD_INTERNAL_MULTI_REQUIRED/[^\/].*/g;
  475.  
  476.     return $regexp;
  477. }
  478.  
  479. sub get_full_path ($) {
  480.     my $originalpath = shift;
  481.  
  482.     my $path = $originalpath;
  483.  
  484.     # keep track so we can break out of loops
  485.     my $linkcount = 0;
  486.  
  487.     # if we don't have any directory foo, look in the current dir
  488.     $path = cwd() . "/$path" if $path !~ m/\//;
  489.  
  490.     # beat symlinks into submission
  491.     while (-l $path) {
  492.  
  493.         if ($linkcount++ > 64) {
  494.             fatal_error "Followed too many symlinks resolving $originalpath";
  495.         }
  496.  
  497.         # split out the directory/file components
  498.         if ($path =~ m/^(.*)\/(.+)$/) {
  499.             my ($dir, $file) = ($1, $2);
  500.  
  501.             # figure out where the link is pointing...
  502.             my $link = readlink($path);
  503.             if ($link =~ /^\//) {
  504.                 # if it's an absolute link, just replace it
  505.                 $path = $link;
  506.             } else {
  507.                 # if it's relative, let abs_path handle it
  508.                 $path = $dir . "/$link";
  509.             }
  510.         }
  511.     }
  512.  
  513.     if (-f $path) {
  514.         my ($dir, $file) = $path =~ m/^(.*)\/(.+)$/;
  515.         $path = realpath($dir) . "/$file";
  516.     } else {
  517.         $path = realpath($path);
  518.     }
  519.  
  520.     return $path;
  521. }
  522.  
  523. sub findexecutable ($) {
  524.     my $bin = shift;
  525.  
  526.     my $fqdbin;
  527.     if (-e $bin) {
  528.         $fqdbin = get_full_path($bin);
  529.         chomp($fqdbin);
  530.     } else {
  531.         if ($bin !~ /\//) {
  532.             my $which = which($bin);
  533.             if ($which) {
  534.                 $fqdbin = get_full_path($which);
  535.             }
  536.         }
  537.     }
  538.  
  539.     unless ($fqdbin && -e $fqdbin) {
  540.         return undef;
  541.     }
  542.  
  543.     return $fqdbin;
  544. }
  545.  
  546. sub name_to_prof_filename($) {
  547.     my $bin    = shift;
  548.     my $filename;
  549.  
  550.     unless ($bin =~ /^($profiledir)/) {
  551.     my $fqdbin = findexecutable($bin);
  552.     if ($fqdbin) {
  553.         $filename = getprofilefilename($fqdbin);
  554.         return ($filename, $fqdbin) if -f $filename;
  555.     }
  556.     }
  557.  
  558.     if ($bin =~ /^$profiledir(.*)/) {
  559.     my $profile = $1;
  560.     return ($bin, $profile);
  561.     } elsif ($bin =~ /^\//) {
  562.     $filename = getprofilefilename($bin);
  563.     return ($filename, $bin);
  564.     } else {
  565.     # not an absolute path try it as a profile_
  566.     $bin = $1 if ($bin !~ /^profile_(.*)/);
  567.     $filename = getprofilefilename($bin);
  568.     return ($filename, "profile_${bin}");
  569.     }
  570.     return undef;
  571. }
  572.  
  573. sub complain ($) {
  574.     my $bin = shift;
  575.  
  576.     return if (!$bin);
  577.  
  578.     my ($filename, $name) = name_to_prof_filename($bin)
  579.     or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin));
  580.  
  581.     UI_Info(sprintf(gettext('Setting %s to complain mode.'), $name));
  582.  
  583.     setprofileflags($filename, "complain");
  584. }
  585.  
  586. sub enforce ($) {
  587.     my $bin = shift;
  588.  
  589.     return if (!$bin);
  590.  
  591.     my ($filename, $name) = name_to_prof_filename($bin)
  592.     or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin));
  593.  
  594.     UI_Info(sprintf(gettext('Setting %s to enforce mode.'), $name));
  595.  
  596.     setprofileflags($filename, "");
  597. }
  598.  
  599. sub head ($) {
  600.     my $file = shift;
  601.  
  602.     my $first = "";
  603.     if (open(FILE, $file)) {
  604.         $first = <FILE>;
  605.         close(FILE);
  606.     }
  607.  
  608.     return $first;
  609. }
  610.  
  611. sub get_output (@) {
  612.     my ($program, @args) = @_;
  613.  
  614.     my $ret = -1;
  615.  
  616.     my $pid;
  617.     my @output;
  618.  
  619.     if (-x $program) {
  620.         $pid = open(KID_TO_READ, "-|");
  621.         unless (defined $pid) {
  622.             fatal_error "can't fork: $!";
  623.         }
  624.  
  625.         if ($pid) {
  626.             while (<KID_TO_READ>) {
  627.                 chomp;
  628.                 push @output, $_;
  629.             }
  630.             close(KID_TO_READ);
  631.             $ret = $?;
  632.         } else {
  633.             ($>, $)) = ($<, $();
  634.             open(STDERR, ">&STDOUT")
  635.               || fatal_error "can't dup stdout to stderr";
  636.             exec($program, @args) || fatal_error "can't exec program: $!";
  637.  
  638.             # NOTREACHED
  639.         }
  640.     }
  641.  
  642.     return ($ret, @output);
  643. }
  644.  
  645. sub get_reqs ($) {
  646.     my $file = shift;
  647.  
  648.     my @reqs;
  649.     my ($ret, @ldd) = get_output($ldd, $file);
  650.  
  651.     if ($ret == 0) {
  652.         for my $line (@ldd) {
  653.             last if $line =~ /not a dynamic executable/;
  654.             last if $line =~ /cannot read header/;
  655.             last if $line =~ /statically linked/;
  656.  
  657.             # avoid new kernel 2.6 poo
  658.             next if $line =~ /linux-(gate|vdso(32|64)).so/;
  659.  
  660.             if ($line =~ /^\s*\S+ => (\/\S+)/) {
  661.                 push @reqs, $1;
  662.             } elsif ($line =~ /^\s*(\/\S+)/) {
  663.                 push @reqs, $1;
  664.             }
  665.         }
  666.     }
  667.  
  668.     return @reqs;
  669. }
  670.  
  671. sub handle_binfmt ($$) {
  672.     my ($profile, $fqdbin) = @_;
  673.  
  674.     my %reqs;
  675.     my @reqs = get_reqs($fqdbin);
  676.  
  677.     while (my $library = shift @reqs) {
  678.  
  679.         $library = get_full_path($library);
  680.  
  681.         push @reqs, get_reqs($library) unless $reqs{$library}++;
  682.  
  683.         # does path match anything pulled in by includes in original profile?
  684.         my $combinedmode = match_prof_incs_to_path($profile, 'allow', $library);
  685.  
  686.         # if we found any matching entries, do the modes match?
  687.         next if $combinedmode;
  688.  
  689.         $library = globcommon($library);
  690.         chomp $library;
  691.         next unless $library;
  692.  
  693.         $profile->{allow}{path}->{$library}{mode} |= str_to_mode("mr");
  694.         $profile->{allow}{path}->{$library}{audit} |= 0;
  695.     }
  696. }
  697.  
  698. sub get_inactive_profile {
  699.     my $fqdbin = shift;
  700.     if ( $extras{$fqdbin} ) {
  701.         return {$fqdbin => $extras{$fqdbin}};
  702.     }
  703. }
  704.  
  705.  
  706.  
  707. sub create_new_profile {
  708.     my $fqdbin = shift;
  709.  
  710.     my $profile;
  711.     if ($fqdbin =~ /^\// ) {
  712.     $profile = {
  713.         $fqdbin => {
  714.         flags   => "complain",
  715.         include => { "abstractions/base" => 1    },
  716.         path    => { $fqdbin => { mode => str_to_mode("mr") } },
  717.         }
  718.     };
  719.     } else {
  720.     $profile = {
  721.         $fqdbin => {
  722.         flags   => "complain",
  723.         include => { "abstractions/base" => 1    },
  724.         }
  725.     };
  726.     }
  727.  
  728.     # if the executable exists on this system, pull in extra dependencies
  729.     if (-f $fqdbin) {
  730.         my $hashbang = head($fqdbin);
  731.         if ($hashbang && $hashbang =~ /^#!\s*(\S+)/) {
  732.             my $interpreter = get_full_path($1);
  733.             $profile->{$fqdbin}{allow}{path}->{$interpreter}{mode} |= str_to_mode("ix");
  734.             $profile->{$fqdbin}{allow}{path}->{$interpreter}{audit} |= 0;
  735.             if ($interpreter =~ /perl/) {
  736.                 $profile->{$fqdbin}{include}->{"abstractions/perl"} = 1;
  737.             } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
  738.                 $profile->{$fqdbin}{include}->{"abstractions/bash"} = 1;
  739.             }
  740.             handle_binfmt($profile->{$fqdbin}, $interpreter);
  741.         } else {
  742.           handle_binfmt($profile->{$fqdbin}, $fqdbin);
  743.         }
  744.     }
  745.  
  746.     # create required infrastructure hats if it's a known change_hat app
  747.     for my $hatglob (keys %{$cfg->{required_hats}}) {
  748.         if ($fqdbin =~ /$hatglob/) {
  749.             for my $hat (sort split(/\s+/, $cfg->{required_hats}{$hatglob})) {
  750.                 $profile->{$hat} = { flags => "complain" };
  751.             }
  752.         }
  753.     }
  754.     push @created, $fqdbin;
  755.     return { $fqdbin => $profile };
  756. }
  757.  
  758. sub delete_profile ($) {
  759.     my $profile = shift;
  760.     my $profilefile = getprofilefilename( $profile );
  761.     if ( -e $profilefile ) {
  762.       unlink( $profilefile );
  763.     }
  764.     if ( defined $sd{$profile} ) {
  765.         delete $sd{$profile};
  766.     }
  767. }
  768.  
  769. sub get_profile {
  770.     my $fqdbin = shift;
  771.     my $profile_data;
  772.  
  773.     my $distro     = $cfg->{repository}{distro};
  774.     my $repo_url   = $cfg->{repository}{url};
  775.     my @profiles;
  776.     my %profile_hash;
  777.  
  778.     if (repo_is_enabled()) {
  779.        my $results;
  780.        UI_BusyStart( gettext("Connecting to repository.....") );
  781.  
  782.        my ($status_ok,$ret) =
  783.            fetch_profiles_by_name($repo_url, $distro, $fqdbin );
  784.        UI_BusyStop();
  785.        if ( $status_ok ) {
  786.            %profile_hash = %$ret;
  787.        } else {
  788.            my $errmsg =
  789.              sprintf(gettext("WARNING: Error fetching profiles from the repository:\n%s\n"),
  790.                      $ret?$ret:gettext("UNKNOWN ERROR"));
  791.            UI_Important( $errmsg );
  792.        }
  793.     }
  794.  
  795.     my $inactive_profile = get_inactive_profile($fqdbin);
  796.     if ( defined $inactive_profile && $inactive_profile ne "" ) {
  797.         # set the profile to complain mode
  798.         my $uname = gettext( "Inactive local profile for ") . $fqdbin;
  799.         $inactive_profile->{$fqdbin}{$fqdbin}{flags} = "complain";
  800.     # inactive profiles store where they came from
  801.     delete $inactive_profile->{$fqdbin}{$fqdbin}{filename};
  802.         $profile_hash{$uname} =
  803.             {
  804.               "username"     => $uname,
  805.               "profile_type" => "INACTIVE_LOCAL",
  806.               "profile"      => serialize_profile($inactive_profile->{$fqdbin},
  807.                                   $fqdbin
  808.                                 ),
  809.               "profile_data" => $inactive_profile,
  810.             };
  811.     }
  812.  
  813.     return undef if ( keys %profile_hash == 0 ); # No repo profiles, no inactive
  814.                                             # profile
  815.     my @options;
  816.     my @tmp_list;
  817.     my $preferred_present = 0;
  818.     my $preferred_user  = $cfg->{repository}{preferred_user} || "NOVELL";
  819.  
  820.     foreach my $p ( keys %profile_hash ) {
  821.         if ( $profile_hash{$p}->{username} eq $preferred_user ) {
  822.              $preferred_present = 1;
  823.         } else {
  824.             push @tmp_list, $profile_hash{$p}->{username};
  825.         }
  826.     }
  827.  
  828.     if ( $preferred_present ) {
  829.         push  @options, $preferred_user;
  830.     }
  831.     push  @options, @tmp_list;
  832.  
  833.     my $q = {};
  834.     $q->{headers} = [];
  835.     push @{ $q->{headers} }, gettext("Profile"), $fqdbin;
  836.  
  837.     $q->{functions} = [ "CMD_VIEW_PROFILE", "CMD_USE_PROFILE",
  838.                         "CMD_CREATE_PROFILE", "CMD_ABORT", "CMD_FINISHED" ];
  839.  
  840.     $q->{default} = "CMD_VIEW_PROFILE";
  841.  
  842.     $q->{options}  = [@options];
  843.     $q->{selected} = 0;
  844.  
  845.     my ($p, $ans, $arg);
  846.     do {
  847.         ($ans, $arg) = UI_PromptUser($q);
  848.         $p = $profile_hash{$options[$arg]};
  849.         for (my $i = 0; $i < scalar(@options); $i++) {
  850.             if ($options[$i] eq $options[$arg]) {
  851.                 $q->{selected} = $i;
  852.             }
  853.         }
  854.  
  855.         if ($ans eq "CMD_VIEW_PROFILE") {
  856.             if ($UI_Mode eq "yast") {
  857.                 SendDataToYast(
  858.                     {
  859.                         type         => "dialog-view-profile",
  860.                         user         => $options[$arg],
  861.                         profile      => $p->{profile},
  862.                         profile_type => $p->{profile_type}
  863.                     }
  864.                 );
  865.                 my ($ypath, $yarg) = GetDataFromYast();
  866.             } else {
  867.                 my $pager = get_pager();
  868.                 open(PAGER, "| $pager");
  869.                 print PAGER gettext("Profile submitted by") .
  870.                                     " $options[$arg]:\n\n" . $p->{profile} . "\n\n";
  871.                 close(PAGER);
  872.             }
  873.         } elsif ($ans eq "CMD_USE_PROFILE") {
  874.             if ( $p->{profile_type} eq "INACTIVE_LOCAL" ) {
  875.                 $profile_data = $p->{profile_data};
  876.                 push @created, $fqdbin; # This really is ugly here
  877.                                         # need to find a better place to mark
  878.                                         # this as newly created
  879.             } else {
  880.                 $profile_data =
  881.                     parse_repo_profile($fqdbin, $repo_url, $p);
  882.             }
  883.         }
  884.     } until ($ans =~ /^CMD_(USE_PROFILE|CREATE_PROFILE)$/);
  885.  
  886.     return $profile_data;
  887. }
  888.  
  889. sub activate_repo_profiles ($$$) {
  890.     my ($url,$profiles,$complain) = @_;
  891.  
  892.     readprofiles();
  893.     eval {
  894.         for my $p ( @$profiles ) {
  895.             my $pname = $p->[0];
  896.             my $profile_data = parse_repo_profile( $pname, $url, $p->[1] );
  897.             attach_profile_data(\%sd, $profile_data);
  898.             writeprofile($pname);
  899.             if ( $complain ) {
  900.                 my $filename = getprofilefilename($pname);
  901.                 setprofileflags($filename, "complain");
  902.                 UI_Info(sprintf(gettext('Setting %s to complain mode.'),
  903.                                         $pname));
  904.             }
  905.         }
  906.     };
  907.     # if there were errors....
  908.     if ($@) {
  909.         $@ =~ s/\n$//;
  910.         print STDERR sprintf(gettext("Error activating profiles: %s\n"), $@);
  911.     }
  912. }
  913.  
  914. sub autodep_base($$) {
  915.     my ($bin, $pname) = @_;
  916.     %extras = ();
  917.  
  918.     $bin = $pname if (! $bin) && ($pname =~ /^\//);
  919.  
  920.     unless ($repo_cfg || not defined $cfg->{repository}{url}) {
  921.         $repo_cfg = read_config("repository.conf");
  922.         if ( (not defined $repo_cfg->{repository}) ||
  923.              ($repo_cfg->{repository}{enabled} eq "later") ) {
  924.                 UI_ask_to_enable_repo();
  925.         }
  926.     }
  927.  
  928.     my $fqdbin;
  929.     if ($bin) {
  930.     # findexecutable() might fail if we're running on a different system
  931.     # than the logs were collected on.  ugly.  we'll just hope for the best.
  932.     $fqdbin = findexecutable($bin) || $bin;
  933.  
  934.     # try to make sure we have a full path in case findexecutable failed
  935.     return unless $fqdbin =~ /^\//;
  936.  
  937.     # ignore directories
  938.     return if -d $fqdbin;
  939.     }
  940.  
  941.     $pname = $fqdbin if $fqdbin;
  942.  
  943.     my $profile_data;
  944.  
  945.     readinactiveprofiles(); # need to read the profiles to see if an
  946.                             # inactive local profile is present
  947.     $profile_data = eval { get_profile($pname) };
  948.  
  949.     unless ($profile_data) {
  950.         $profile_data = create_new_profile($pname);
  951.     }
  952.  
  953.     my $file = getprofilefilename($pname);
  954.  
  955.     # stick the profile into our data structure.
  956.     attach_profile_data(\%sd, $profile_data);
  957.     # and store a "clean" version also so we can display the changes we've
  958.     # made during this run
  959.     attach_profile_data(\%original_sd, $profile_data);
  960.  
  961.     if (-f "$profiledir/tunables/global") {
  962.         unless (exists $filelist{$file}) {
  963.             $filelist{$file} = { };
  964.         }
  965.         $filelist{$file}{include}{'tunables/global'} = 1; # sorry
  966.     }
  967.  
  968.     # write out the profile...
  969.     writeprofile_ui_feedback($pname);
  970. }
  971.  
  972. sub autodep ($) {
  973.     my $bin = shift;
  974.     return autodep_base($bin, "");
  975. }
  976.  
  977. sub getprofilefilename ($) {
  978.     my $profile = shift;
  979.  
  980.     my $filename = $profile;
  981.     if ($filename =~ /^\//) {
  982.     $filename =~ s/^\///;                              # strip leading /
  983.     } else {
  984.     $filename = "profile_$filename";
  985.     }
  986.     $filename =~ s/\//./g;                            # convert /'s to .'s
  987.  
  988.     return "$profiledir/$filename";
  989. }
  990.  
  991. sub setprofileflags ($$) {
  992.     my $filename = shift;
  993.     my $newflags = shift;
  994.  
  995.     if (open(PROFILE, "$filename")) {
  996.         if (open(NEWPROFILE, ">$filename.new")) {
  997.             while (<PROFILE>) {
  998.                 if (m/^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/) {
  999.                     my ($binary, $flags) = ($1, $5);
  1000.  
  1001.                     if ($newflags) {
  1002.                         $_ = "$binary flags=($newflags) {\n";
  1003.                     } else {
  1004.                         $_ = "$binary {\n";
  1005.                     }
  1006.                 } elsif (m/^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$/) {
  1007.                     my ($hat, $flags) = ($1, $2);
  1008.  
  1009.                     if ($newflags) {
  1010.                         $_ = "$hat flags=($newflags) {\n";
  1011.                     } else {
  1012.                         $_ = "$hat {\n";
  1013.                     }
  1014.                 }
  1015.                 print NEWPROFILE;
  1016.             }
  1017.             close(NEWPROFILE);
  1018.             rename("$filename.new", "$filename");
  1019.         }
  1020.         close(PROFILE);
  1021.     }
  1022. }
  1023.  
  1024. sub profile_exists($) {
  1025.     my $program = shift || return 0;
  1026.  
  1027.     # if it's already in the cache, return true
  1028.     return 1 if $existing_profiles{$program};
  1029.  
  1030.     # if the profile exists, mark it in the cache and return true
  1031.     my $profile = getprofilefilename($program);
  1032.     if (-e $profile) {
  1033.         $existing_profiles{$program} = 1;
  1034.         return 1;
  1035.     }
  1036.  
  1037.     # couldn't find a profile, so we'll return false
  1038.     return 0;
  1039. }
  1040.  
  1041. sub sync_profiles {
  1042.  
  1043.     my ($user, $pass) = get_repo_user_pass();
  1044.     return unless ( $user && $pass );
  1045.  
  1046.     my @repo_profiles;
  1047.     my @changed_profiles;
  1048.     my @new_profiles;
  1049.     my $serialize_opts = { };
  1050.     my ($status_ok,$ret) =
  1051.         fetch_profiles_by_user($cfg->{repository}{url},
  1052.                                $cfg->{repository}{distro},
  1053.                                $user
  1054.                               );
  1055.     if ( !$status_ok ) {
  1056.         my $errmsg =
  1057.           sprintf(gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"),
  1058.                   $ret?$ret:gettext("UNKNOWN ERROR"));
  1059.         UI_Important($errmsg);
  1060.         return;
  1061.     } else {
  1062.         my $users_repo_profiles = $ret;
  1063.         $serialize_opts->{NO_FLAGS} = 1;
  1064.         #
  1065.         # Find changes made to non-repo profiles
  1066.         #
  1067.         for my $profile (sort keys %sd) {
  1068.             if (is_repo_profile($sd{$profile}{$profile})) {
  1069.                 push @repo_profiles, $profile;
  1070.             }
  1071.             if ( grep(/^$profile$/, @created) )  {
  1072.                 my $p_local = serialize_profile($sd{$profile},
  1073.                                                 $profile,
  1074.                                                 $serialize_opts);
  1075.                 if ( not defined $users_repo_profiles->{$profile} ) {
  1076.                     push @new_profiles,  [ $profile, $p_local, "" ];
  1077.                 } else {
  1078.                     my $p_repo = $users_repo_profiles->{$profile}->{profile};
  1079.                     if ( $p_local ne $p_repo ) {
  1080.                         push @changed_profiles, [ $profile, $p_local, $p_repo ];
  1081.                     }
  1082.                 }
  1083.             }
  1084.         }
  1085.  
  1086.         #
  1087.         # Find changes made to local profiles with repo metadata
  1088.         #
  1089.         if (@repo_profiles) {
  1090.             for my $profile (@repo_profiles) {
  1091.                 my $p_local = serialize_profile($sd{$profile},
  1092.                                                 $profile,
  1093.                                                 $serialize_opts);
  1094.                 if ( not exists $users_repo_profiles->{$profile} ) {
  1095.                     push @new_profiles,  [ $profile, $p_local, "" ];
  1096.                 } else {
  1097.                     my $p_repo = "";
  1098.                     if ( $sd{$profile}{$profile}{repo}{user} eq $user ) {
  1099.                        $p_repo = $users_repo_profiles->{$profile}->{profile};
  1100.                     }  else {
  1101.                         my ($status_ok,$ret) =
  1102.                             fetch_profile_by_id($cfg->{repository}{url},
  1103.                                                 $sd{$profile}{$profile}{repo}{id}
  1104.                                                );
  1105.                         if ( $status_ok ) {
  1106.                            $p_repo = $ret->{profile};
  1107.                         } else {
  1108.                             my $errmsg =
  1109.                               sprintf(
  1110.                                 gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"),
  1111.                                 $ret?$ret:gettext("UNKNOWN ERROR"));
  1112.                             UI_Important($errmsg);
  1113.                             next;
  1114.                         }
  1115.                     }
  1116.                     if ( $p_repo ne $p_local ) {
  1117.                         push @changed_profiles, [ $profile, $p_local, $p_repo ];
  1118.                     }
  1119.                 }
  1120.             }
  1121.         }
  1122.  
  1123.         if ( @changed_profiles ) {
  1124.            submit_changed_profiles( \@changed_profiles );
  1125.         }
  1126.         if ( @new_profiles ) {
  1127.            submit_created_profiles( \@new_profiles );
  1128.         }
  1129.     }
  1130. }
  1131.  
  1132. sub submit_created_profiles {
  1133.     my $new_profiles = shift;
  1134.     my $url = $cfg->{repository}{url};
  1135.  
  1136.     if ($UI_Mode eq "yast") {
  1137.         my $title       = gettext("New profiles");
  1138.         my $explanation =
  1139.           gettext("Please choose the newly created profiles that you would".
  1140.           " like\nto store in the repository");
  1141.         yast_select_and_upload_profiles($title,
  1142.                                         $explanation,
  1143.                                         $new_profiles);
  1144.     } else {
  1145.         my $title       =
  1146.           gettext("Submit newly created profiles to the repository");
  1147.         my $explanation =
  1148.           gettext("Would you like to upload the newly created profiles?");
  1149.         console_select_and_upload_profiles($title,
  1150.                                            $explanation,
  1151.                                            $new_profiles);
  1152.     }
  1153. }
  1154.  
  1155. sub submit_changed_profiles {
  1156.     my $changed_profiles = shift;
  1157.     my $url = $cfg->{repository}{url};
  1158.     if (@$changed_profiles) {
  1159.         if ($UI_Mode eq "yast") {
  1160.             my $explanation =
  1161.               gettext("Select which of the changed profiles you would".
  1162.               " like to upload\nto the repository");
  1163.             my $title       = gettext("Changed profiles");
  1164.             yast_select_and_upload_profiles($title,
  1165.                                             $explanation,
  1166.                                             $changed_profiles);
  1167.         } else {
  1168.             my $title       =
  1169.               gettext("Submit changed profiles to the repository");
  1170.             my $explanation =
  1171.               gettext("The following profiles from the repository were".
  1172.               " changed.\nWould you like to upload your changes?");
  1173.             console_select_and_upload_profiles($title,
  1174.                                                $explanation,
  1175.                                                $changed_profiles);
  1176.         }
  1177.     }
  1178. }
  1179.  
  1180. sub yast_select_and_upload_profiles {
  1181.  
  1182.     my ($title, $explanation, $profiles_ref) = @_;
  1183.     my $url = $cfg->{repository}{url};
  1184.     my %profile_changes;
  1185.     my @profiles = @$profiles_ref;
  1186.  
  1187.     foreach my $prof (@profiles) {
  1188.         $profile_changes{ $prof->[0] } =
  1189.           get_profile_diff($prof->[2], $prof->[1]);
  1190.     }
  1191.  
  1192.     my (@selected_profiles, $changelog, $changelogs, $single_changelog);
  1193.     SendDataToYast(
  1194.         {
  1195.             type               => "dialog-select-profiles",
  1196.             title              => $title,
  1197.             explanation        => $explanation,
  1198.             default_select     => "false",
  1199.             disable_ask_upload => "true",
  1200.             profiles           => \%profile_changes
  1201.         }
  1202.     );
  1203.     my ($ypath, $yarg) = GetDataFromYast();
  1204.     if ($yarg->{STATUS} eq "cancel") {
  1205.         return;
  1206.     } else {
  1207.         my $selected_profiles_ref = $yarg->{PROFILES};
  1208.         @selected_profiles = @$selected_profiles_ref;
  1209.         $changelogs        = $yarg->{CHANGELOG};
  1210.         if (defined $changelogs->{SINGLE_CHANGELOG}) {
  1211.             $changelog        = $changelogs->{SINGLE_CHANGELOG};
  1212.             $single_changelog = 1;
  1213.         }
  1214.     }
  1215.  
  1216.     for my $profile (@selected_profiles) {
  1217.         my ($user, $pass) = get_repo_user_pass();
  1218.         my $profile_string = serialize_profile($sd{$profile}, $profile);
  1219.         if (!$single_changelog) {
  1220.             $changelog = $changelogs->{$profile};
  1221.         }
  1222.         my ($status_ok, $ret) = upload_profile( $url,
  1223.                                                 $user,
  1224.                                                 $pass,
  1225.                                                 $cfg->{repository}{distro},
  1226.                                                 $profile,
  1227.                                                 $profile_string,
  1228.                                                 $changelog
  1229.                                               );
  1230.         if ($status_ok) {
  1231.             my $newprofile = $ret;
  1232.             my $newid      = $newprofile->{id};
  1233.             set_repo_info($sd{$profile}{$profile}, $url, $user, $newid);
  1234.             writeprofile_ui_feedback($profile);
  1235.         } else {
  1236.             my $errmsg =
  1237.               sprintf(
  1238.                 gettext("WARNING: An error occured while uploading the profile %s\n%s\n"),
  1239.                 $profile, $ret?$ret:gettext("UNKNOWN ERROR"));
  1240.             UI_Important( $errmsg );
  1241.         }
  1242.     }
  1243.     UI_Info(gettext("Uploaded changes to repository."));
  1244.  
  1245.     # Check to see if unselected profiles should be marked as local only
  1246.     # this is outside of the main repo code as we want users to be able to mark
  1247.     # profiles as local only even if they aren't able to connect to the repo.
  1248.     if (defined $yarg->{NEVER_ASK_AGAIN}) {
  1249.         my @unselected_profiles;
  1250.         foreach my $prof (@profiles) {
  1251.             if ( grep(/^$prof->[0]$/, @selected_profiles) == 0 ) {
  1252.                 push @unselected_profiles, $prof->[0];
  1253.             }
  1254.         }
  1255.         set_profiles_local_only( @unselected_profiles );
  1256.     }
  1257. }
  1258.  
  1259. sub console_select_and_upload_profiles {
  1260.     my ($title, $explanation, $profiles_ref) = @_;
  1261.     my $url = $cfg->{repository}{url};
  1262.     my @profiles = @$profiles_ref;
  1263.     my $q = {};
  1264.     $q->{title} = $title;
  1265.     $q->{headers} = [ gettext("Repository"), $url, ];
  1266.  
  1267.     $q->{explanation} = $explanation;
  1268.  
  1269.     $q->{functions} = [ "CMD_UPLOAD_CHANGES",
  1270.                         "CMD_VIEW_CHANGES",
  1271.                         "CMD_ASK_LATER",
  1272.                         "CMD_ASK_NEVER",
  1273.                         "CMD_ABORT", ];
  1274.  
  1275.     $q->{default} = "CMD_VIEW_CHANGES";
  1276.  
  1277.     $q->{options} = [ map { $_->[0] } @profiles ];
  1278.     $q->{selected} = 0;
  1279.  
  1280.     my ($ans, $arg);
  1281.     do {
  1282.         ($ans, $arg) = UI_PromptUser($q);
  1283.  
  1284.         if ($ans eq "CMD_VIEW_CHANGES") {
  1285.             display_changes($profiles[$arg]->[2], $profiles[$arg]->[1]);
  1286.         }
  1287.     } until $ans =~ /^CMD_(UPLOAD_CHANGES|ASK_NEVER|ASK_LATER)/;
  1288.  
  1289.     if ($ans eq "CMD_ASK_NEVER") {
  1290.         set_profiles_local_only(  map { $_->[0] } @profiles  );
  1291.     } elsif ($ans eq "CMD_UPLOAD_CHANGES") {
  1292.         my $changelog = UI_GetString(gettext("Changelog Entry: "), "");
  1293.         my ($user, $pass) = get_repo_user_pass();
  1294.         if ($user && $pass) {
  1295.             for my $p_data (@profiles) {
  1296.                 my $profile          = $p_data->[0];
  1297.                 my $profile_string   = $p_data->[1];
  1298.                 my ($status_ok,$ret) =
  1299.                     upload_profile( $url,
  1300.                                     $user,
  1301.                                     $pass,
  1302.                                     $cfg->{repository}{distro},
  1303.                                     $profile,
  1304.                                     $profile_string,
  1305.                                     $changelog
  1306.                                   );
  1307.                 if ($status_ok) {
  1308.                     my $newprofile = $ret;
  1309.                     my $newid      = $newprofile->{id};
  1310.                     set_repo_info($sd{$profile}{$profile}, $url, $user, $newid);
  1311.                     writeprofile_ui_feedback($profile);
  1312.                     UI_Info(
  1313.                       sprintf(gettext("Uploaded %s to repository."), $profile)
  1314.                     );
  1315.                 } else {
  1316.                     my $errmsg =
  1317.                       sprintf(
  1318.                         gettext("WARNING: An error occured while uploading the profile %s\n%s\n"),
  1319.                         $profile, $ret?$ret:gettext("UNKNOWN ERROR"));
  1320.                     UI_Important( $errmsg );
  1321.                 }
  1322.             }
  1323.         } else {
  1324.             UI_Important(gettext("Repository Error\n" .
  1325.                       "Registration or Signin was unsuccessful. User login\n" .
  1326.                       "information is required to upload profiles to the\n" .
  1327.                       "repository. These changes have not been sent.\n"));
  1328.         }
  1329.     }
  1330. }
  1331.  
  1332. #
  1333. # Mark the profiles passed in @profiles as local only
  1334. # and don't prompt to upload changes to the repository
  1335. #
  1336. sub set_profiles_local_only {
  1337.     my @profiles = @_;
  1338.     for my $profile (@profiles) {
  1339.          $sd{$profile}{$profile}{repo}{neversubmit} = 1;
  1340.          writeprofile_ui_feedback($profile);
  1341.     }
  1342. }
  1343.  
  1344. ##########################################################################
  1345. # Here are the console/yast interface functions
  1346.  
  1347. sub UI_Info ($) {
  1348.     my $text = shift;
  1349.  
  1350.     $DEBUGGING && debug "UI_Info: $UI_Mode: $text";
  1351.  
  1352.     if ($UI_Mode eq "text") {
  1353.         print "$text\n";
  1354.     } else {
  1355.         ycp::y2milestone($text);
  1356.     }
  1357. }
  1358.  
  1359. sub UI_Important ($) {
  1360.     my $text = shift;
  1361.  
  1362.     $DEBUGGING && debug "UI_Important: $UI_Mode: $text";
  1363.  
  1364.     if ($UI_Mode eq "text") {
  1365.         print "\n$text\n";
  1366.     } else {
  1367.         SendDataToYast({ type => "dialog-error", message => $text });
  1368.         my ($path, $yarg) = GetDataFromYast();
  1369.     }
  1370. }
  1371.  
  1372. sub UI_YesNo ($$) {
  1373.     my $text    = shift;
  1374.     my $default = shift;
  1375.  
  1376.     $DEBUGGING && debug "UI_YesNo: $UI_Mode: $text $default";
  1377.  
  1378.     my $ans;
  1379.     if ($UI_Mode eq "text") {
  1380.  
  1381.         my $yes = gettext("(Y)es");
  1382.         my $no  = gettext("(N)o");
  1383.  
  1384.         # figure out our localized hotkeys
  1385.         my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for");
  1386.         $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'";
  1387.         my $yeskey = lc($1);
  1388.         $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'";
  1389.         my $nokey = lc($1);
  1390.  
  1391.         print "\n$text\n";
  1392.         if ($default eq "y") {
  1393.             print "\n[$yes] / $no\n";
  1394.         } else {
  1395.             print "\n$yes / [$no]\n";
  1396.         }
  1397.         $ans = getkey() || (($default eq "y") ? $yeskey : $nokey);
  1398.  
  1399.         # convert back from a localized answer to english y or n
  1400.         $ans = (lc($ans) eq $yeskey) ? "y" : "n";
  1401.     } else {
  1402.  
  1403.         SendDataToYast({ type => "dialog-yesno", question => $text });
  1404.         my ($ypath, $yarg) = GetDataFromYast();
  1405.         $ans = $yarg->{answer} || $default;
  1406.  
  1407.     }
  1408.  
  1409.     return $ans;
  1410. }
  1411.  
  1412. sub UI_YesNoCancel ($$) {
  1413.     my $text    = shift;
  1414.     my $default = shift;
  1415.  
  1416.     $DEBUGGING && debug "UI_YesNoCancel: $UI_Mode: $text $default";
  1417.  
  1418.     my $ans;
  1419.     if ($UI_Mode eq "text") {
  1420.  
  1421.         my $yes    = gettext("(Y)es");
  1422.         my $no     = gettext("(N)o");
  1423.         my $cancel = gettext("(C)ancel");
  1424.  
  1425.         # figure out our localized hotkeys
  1426.         my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for");
  1427.         $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'";
  1428.         my $yeskey = lc($1);
  1429.         $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'";
  1430.         my $nokey = lc($1);
  1431.         $cancel =~ /\((\S)\)/ or fatal_error "$usrmsg '$cancel'";
  1432.         my $cancelkey = lc($1);
  1433.  
  1434.         $ans = "XXXINVALIDXXX";
  1435.         while ($ans !~ /^(y|n|c)$/) {
  1436.             print "\n$text\n";
  1437.             if ($default eq "y") {
  1438.                 print "\n[$yes] / $no / $cancel\n";
  1439.             } elsif ($default eq "n") {
  1440.                 print "\n$yes / [$no] / $cancel\n";
  1441.             } else {
  1442.                 print "\n$yes / $no / [$cancel]\n";
  1443.             }
  1444.  
  1445.             $ans = getkey();
  1446.  
  1447.             if ($ans) {
  1448.                 # convert back from a localized answer to english y or n
  1449.                 $ans = lc($ans);
  1450.                 if ($ans eq $yeskey) {
  1451.                     $ans = "y";
  1452.                 } elsif ($ans eq $nokey) {
  1453.                     $ans = "n";
  1454.                 } elsif ($ans eq $cancelkey) {
  1455.                     $ans = "c";
  1456.                 }
  1457.             } else {
  1458.                 $ans = $default;
  1459.             }
  1460.         }
  1461.     } else {
  1462.  
  1463.         SendDataToYast({ type => "dialog-yesnocancel", question => $text });
  1464.         my ($ypath, $yarg) = GetDataFromYast();
  1465.         $ans = $yarg->{answer} || $default;
  1466.  
  1467.     }
  1468.  
  1469.     return $ans;
  1470. }
  1471.  
  1472. sub UI_GetString ($$) {
  1473.     my $text    = shift;
  1474.     my $default = shift;
  1475.  
  1476.     $DEBUGGING && debug "UI_GetString: $UI_Mode: $text $default";
  1477.  
  1478.     my $string;
  1479.     if ($UI_Mode eq "text") {
  1480.  
  1481.         if ($term) {
  1482.             $string = $term->readline($text, $default);
  1483.         } else {
  1484.             local $| = 1;
  1485.             print "$text";
  1486.             $string = <STDIN>;
  1487.             chomp($string);
  1488.         }
  1489.  
  1490.     } else {
  1491.  
  1492.         SendDataToYast({
  1493.             type    => "dialog-getstring",
  1494.             label   => $text,
  1495.             default => $default
  1496.         });
  1497.         my ($ypath, $yarg) = GetDataFromYast();
  1498.         $string = $yarg->{string};
  1499.  
  1500.     }
  1501.     return $string;
  1502. }
  1503.  
  1504. sub UI_GetFile ($) {
  1505.     my $f = shift;
  1506.  
  1507.     $DEBUGGING && debug "UI_GetFile: $UI_Mode";
  1508.  
  1509.     my $filename;
  1510.     if ($UI_Mode eq "text") {
  1511.  
  1512.         local $| = 1;
  1513.         print "$f->{description}\n";
  1514.         $filename = <STDIN>;
  1515.         chomp($filename);
  1516.  
  1517.     } else {
  1518.  
  1519.         $f->{type} = "dialog-getfile";
  1520.  
  1521.         SendDataToYast($f);
  1522.         my ($ypath, $yarg) = GetDataFromYast();
  1523.         if ($yarg->{answer} eq "okay") {
  1524.             $filename = $yarg->{filename};
  1525.         }
  1526.     }
  1527.  
  1528.     return $filename;
  1529. }
  1530.  
  1531. sub UI_BusyStart ($) {
  1532.     my $message = shift;
  1533.     $DEBUGGING && debug "UI_BusyStart: $UI_Mode";
  1534.  
  1535.     if ($UI_Mode eq "text") {
  1536.       UI_Info( $message );
  1537.     } else {
  1538.         SendDataToYast({
  1539.                         type    => "dialog-busy-start",
  1540.                         message => $message,
  1541.                        });
  1542.         my ($ypath, $yarg) = GetDataFromYast();
  1543.     }
  1544. }
  1545.  
  1546. sub UI_BusyStop  {
  1547.     $DEBUGGING && debug "UI_BusyStop: $UI_Mode";
  1548.  
  1549.     if ($UI_Mode ne "text") {
  1550.         SendDataToYast({ type    => "dialog-busy-stop" });
  1551.         my ($ypath, $yarg) = GetDataFromYast();
  1552.     }
  1553. }
  1554.  
  1555.  
  1556. my %CMDS = (
  1557.     CMD_ALLOW            => "(A)llow",
  1558.     CMD_OTHER         => "(M)ore",
  1559.     CMD_AUDIT_NEW     => "Audi(t)",
  1560.     CMD_AUDIT_OFF     => "Audi(t) off",
  1561.     CMD_AUDIT_FULL     => "Audit (A)ll",
  1562.     CMD_OTHER         => "(O)pts",
  1563.     CMD_USER_ON         => "(O)wner permissions on",
  1564.     CMD_USER_OFF     => "(O)wner permissions off",
  1565.     CMD_DENY             => "(D)eny",
  1566.     CMD_ABORT            => "Abo(r)t",
  1567.     CMD_FINISHED         => "(F)inish",
  1568.     CMD_ix               => "(I)nherit",
  1569.     CMD_px               => "(P)rofile",
  1570.     CMD_px_safe         => "(P)rofile Clean Exec",
  1571.     CMD_cx         => "(C)hild",
  1572.     CMD_cx_safe         => "(C)hild Clean Exec",
  1573.     CMD_nx         => "(N)ame",
  1574.     CMD_nx_safe         => "(N)amed Clean Exec",
  1575.     CMD_ux               => "(U)nconfined",
  1576.     CMD_ux_safe         => "(U)nconfined Clean Exec",
  1577.     CMD_pix         => "(P)rofile ix",
  1578.     CMD_pix_safe     => "(P)rofile ix Clean Exec",
  1579.     CMD_cix         => "(C)hild ix",
  1580.     CMD_cix_safe     => "(C)hild ix Cx Clean Exec",
  1581.     CMD_nix         => "(N)ame ix",
  1582.     CMD_nix_safe     => "(N)ame ix",
  1583.     CMD_EXEC_IX_ON     => "(X)ix",
  1584.     CMD_EXEC_IX_OFF     => "(X)ix",
  1585.     CMD_SAVE             => "(S)ave Changes",
  1586.     CMD_CONTINUE         => "(C)ontinue Profiling",
  1587.     CMD_NEW              => "(N)ew",
  1588.     CMD_GLOB             => "(G)lob",
  1589.     CMD_GLOBEXT          => "Glob w/(E)xt",
  1590.     CMD_ADDHAT           => "(A)dd Requested Hat",
  1591.     CMD_USEDEFAULT       => "(U)se Default Hat",
  1592.     CMD_SCAN             => "(S)can system log for SubDomain events",
  1593.     CMD_HELP             => "(H)elp",
  1594.     CMD_VIEW_PROFILE     => "(V)iew Profile",
  1595.     CMD_USE_PROFILE      => "(U)se Profile",
  1596.     CMD_CREATE_PROFILE   => "(C)reate New Profile",
  1597.     CMD_UPDATE_PROFILE   => "(U)pdate Profile",
  1598.     CMD_IGNORE_UPDATE    => "(I)gnore Update",
  1599.     CMD_SAVE_CHANGES     => "(S)ave Changes",
  1600.     CMD_UPLOAD_CHANGES   => "(U)pload Changes",
  1601.     CMD_VIEW_CHANGES     => "(V)iew Changes",
  1602.     CMD_VIEW             => "(V)iew",
  1603.     CMD_ENABLE_REPO      => "(E)nable Repository",
  1604.     CMD_DISABLE_REPO     => "(D)isable Repository",
  1605.     CMD_ASK_NEVER        => "(N)ever Ask Again",
  1606.     CMD_ASK_LATER        => "Ask Me (L)ater",
  1607.     CMD_YES              => "(Y)es",
  1608.     CMD_NO               => "(N)o",
  1609.     CMD_ALL_NET          => "Allow All (N)etwork",
  1610.     CMD_NET_FAMILY       => "Allow Network Fa(m)ily",
  1611.     CMD_OVERWRITE        => "(O)verwrite Profile",
  1612.     CMD_KEEP             => "(K)eep Profile",
  1613.     CMD_CONTINUE         => "(C)ontinue",
  1614. );
  1615.  
  1616. sub UI_PromptUser ($) {
  1617.     my $q = shift;
  1618.  
  1619.     my ($cmd, $arg);
  1620.     if ($UI_Mode eq "text") {
  1621.  
  1622.         ($cmd, $arg) = Text_PromptUser($q);
  1623.  
  1624.     } else {
  1625.  
  1626.         $q->{type} = "wizard";
  1627.  
  1628.         SendDataToYast($q);
  1629.         my ($ypath, $yarg) = GetDataFromYast();
  1630.  
  1631.         $cmd = $yarg->{selection} || "CMD_ABORT";
  1632.         $arg = $yarg->{selected};
  1633.     }
  1634.  
  1635.     if ($cmd eq "CMD_ABORT") {
  1636.         confirm_and_abort();
  1637.         $cmd = "XXXINVALIDXXX";
  1638.     } elsif ($cmd eq "CMD_FINISHED") {
  1639.         confirm_and_finish();
  1640.         $cmd = "XXXINVALIDXXX";
  1641.     }
  1642.  
  1643.     if (wantarray) {
  1644.         return ($cmd, $arg);
  1645.     } else {
  1646.         return $cmd;
  1647.     }
  1648. }
  1649.  
  1650.  
  1651. sub UI_ShortMessage {
  1652.     my ($headline, $message) = @_;
  1653.  
  1654.     SendDataToYast(
  1655.         {
  1656.             type     => "short-dialog-message",
  1657.             headline => $headline,
  1658.             message  => $message
  1659.         }
  1660.     );
  1661.     my ($ypath, $yarg) = GetDataFromYast();
  1662. }
  1663.  
  1664. sub UI_LongMessage {
  1665.     my ($headline, $message) = @_;
  1666.  
  1667.     $headline = "MISSING" if not defined $headline;
  1668.     $message  = "MISSING" if not defined $message;
  1669.  
  1670.     SendDataToYast(
  1671.         {
  1672.             type     => "long-dialog-message",
  1673.             headline => $headline,
  1674.             message  => $message
  1675.         }
  1676.     );
  1677.     my ($ypath, $yarg) = GetDataFromYast();
  1678. }
  1679.  
  1680. ##########################################################################
  1681. # here are the interface functions to send data back and forth between
  1682. # the yast frontend and the perl backend
  1683.  
  1684. # this is super ugly, but waits for the next ycp Read command and sends data
  1685. # back to the ycp front end.
  1686.  
  1687. sub SendDataToYast {
  1688.     my $data = shift;
  1689.  
  1690.     $DEBUGGING && debug "SendDataToYast: Waiting for YCP command";
  1691.  
  1692.     while (<STDIN>) {
  1693.         $DEBUGGING && debug "SendDataToYast: YCP: $_";
  1694.         my ($ycommand, $ypath, $yargument) = ycp::ParseCommand($_);
  1695.  
  1696.         if ($ycommand && $ycommand eq "Read") {
  1697.  
  1698.             if ($DEBUGGING) {
  1699.                 my $debugmsg = Data::Dumper->Dump([$data], [qw(*data)]);
  1700.                 debug "SendDataToYast: Sending--\n$debugmsg";
  1701.             }
  1702.  
  1703.             ycp::Return($data);
  1704.             return 1;
  1705.  
  1706.         } else {
  1707.  
  1708.             $DEBUGGING && debug "SendDataToYast: Expected 'Read' but got-- $_";
  1709.  
  1710.         }
  1711.     }
  1712.  
  1713.     # if we ever break out here, something's horribly wrong.
  1714.     fatal_error "SendDataToYast: didn't receive YCP command before connection died";
  1715. }
  1716.  
  1717. # this is super ugly, but waits for the next ycp Write command and grabs
  1718. # whatever the ycp front end gives us
  1719.  
  1720. sub GetDataFromYast {
  1721.  
  1722.     $DEBUGGING && debug "GetDataFromYast: Waiting for YCP command";
  1723.  
  1724.     while (<STDIN>) {
  1725.         $DEBUGGING && debug "GetDataFromYast: YCP: $_";
  1726.         my ($ycmd, $ypath, $yarg) = ycp::ParseCommand($_);
  1727.  
  1728.         if ($DEBUGGING) {
  1729.             my $debugmsg = Data::Dumper->Dump([$yarg], [qw(*data)]);
  1730.             debug "GetDataFromYast: Received--\n$debugmsg";
  1731.         }
  1732.  
  1733.         if ($ycmd && $ycmd eq "Write") {
  1734.  
  1735.             ycp::Return("true");
  1736.             return ($ypath, $yarg);
  1737.  
  1738.         } else {
  1739.             $DEBUGGING && debug "GetDataFromYast: Expected 'Write' but got-- $_";
  1740.         }
  1741.     }
  1742.  
  1743.     # if we ever break out here, something's horribly wrong.
  1744.     fatal_error "GetDataFromYast: didn't receive YCP command before connection died";
  1745. }
  1746.  
  1747. sub confirm_and_abort {
  1748.     my $ans = UI_YesNo(gettext("Are you sure you want to abandon this set of profile changes and exit?"), "n");
  1749.     if ($ans eq "y") {
  1750.         UI_Info(gettext("Abandoning all changes."));
  1751.         shutdown_yast();
  1752.         exit 0;
  1753.     }
  1754. }
  1755.  
  1756. sub confirm_and_finish {
  1757.     die "FINISHING\n";
  1758. }
  1759.  
  1760. sub build_x_functions($$$) {
  1761.     my ($default, $options, $exec_toggle) = @_;
  1762.     my @{list};
  1763.     if ($exec_toggle) {
  1764.     push @list, "CMD_ix" if $options =~ /i/;
  1765.     push @list, "CMD_pix" if $options =~ /p/ and $options =~ /i/;
  1766.     push @list, "CMD_cix" if $options =~ /c/ and $options =~ /i/;
  1767.     push @list, "CMD_nix" if $options =~ /n/ and $options =~ /i/;
  1768.     push @list, "CMD_ux" if $options =~ /u/;
  1769.     } else {
  1770.     push @list, "CMD_ix" if $options =~ /i/;
  1771.     push @list, "CMD_px" if $options =~ /p/;
  1772.     push @list, "CMD_cx" if $options =~ /c/;
  1773.     push @list, "CMD_nx" if $options =~ /n/;
  1774.     push @list, "CMD_ux" if $options =~ /u/;
  1775.     }
  1776.     if ($exec_toggle) {
  1777.     push @list, "CMD_EXEC_IX_OFF" if $options =~/p|c|n/;
  1778.     } else {
  1779.     push @list, "CMD_EXEC_IX_ON" if $options =~/p|c|n/;
  1780.     }
  1781.     push @list, "CMD_DENY", "CMD_ABORT", "CMD_FINISHED";
  1782.     return @list;
  1783. }
  1784.  
  1785. ##########################################################################
  1786. # this is the hideously ugly function that descends down the flow/event
  1787. # trees that we've generated by parsing the logfile
  1788.  
  1789. sub handlechildren {
  1790.     my $profile = shift;
  1791.     my $hat     = shift;
  1792.     my $root    = shift;
  1793.  
  1794.     my @entries = @$root;
  1795.     for my $entry (@entries) {
  1796.         fatal_error "$entry is not a ref" if not ref($entry);
  1797.  
  1798.         if (ref($entry->[0])) {
  1799.             handlechildren($profile, $hat, $entry);
  1800.         } else {
  1801.  
  1802.             my @entry = @$entry;
  1803.             my $type  = shift @entry;
  1804.  
  1805.             if ($type eq "fork") {
  1806.                 my ($pid, $p, $h) = @entry;
  1807.  
  1808.                 if (   ($p !~ /null(-complain)*-profile/)
  1809.                     && ($h !~ /null(-complain)*-profile/))
  1810.                 {
  1811.                     $profile = $p;
  1812.                     $hat     = $h;
  1813.                 }
  1814.  
  1815.         if ($hat) {
  1816.             $profilechanges{$pid} = $profile . "//" . $hat;
  1817.         } else {
  1818.             $profilechanges{$pid} = $profile;
  1819.         }
  1820.             } elsif ($type eq "unknown_hat") {
  1821.                 my ($pid, $p, $h, $sdmode, $uhat) = @entry;
  1822.  
  1823.                 if ($p !~ /null(-complain)*-profile/) {
  1824.                     $profile = $p;
  1825.                 }
  1826.  
  1827.                 if ($sd{$profile}{$uhat}) {
  1828.                     $hat = $uhat;
  1829.                     next;
  1830.                 }
  1831.  
  1832.                 my $new_p = update_repo_profile($sd{$profile}{$profile});
  1833.                 if ( $new_p and
  1834.                      UI_SelectUpdatedRepoProfile($profile, $new_p) and
  1835.                      $sd{$profile}{$uhat} ) {
  1836.                     $hat = $uhat;
  1837.                     next;
  1838.                 }
  1839.  
  1840.                 # figure out what our default hat for this application is.
  1841.                 my $defaulthat;
  1842.                 for my $hatglob (keys %{$cfg->{defaulthat}}) {
  1843.                     $defaulthat = $cfg->{defaulthat}{$hatglob}
  1844.                       if $profile =~ /$hatglob/;
  1845.                 }
  1846.                 # keep track of previous answers for this run...
  1847.                 my $context = $profile;
  1848.                 $context .= " -> ^$uhat";
  1849.                 my $ans = $transitions{$context} || "XXXINVALIDXXX";
  1850.  
  1851.                 while ($ans !~ /^CMD_(ADDHAT|USEDEFAULT|DENY)$/) {
  1852.                     my $q = {};
  1853.                     $q->{headers} = [];
  1854.                     push @{ $q->{headers} }, gettext("Profile"), $profile;
  1855.                     if ($defaulthat) {
  1856.                         push @{ $q->{headers} }, gettext("Default Hat"), $defaulthat;
  1857.                     }
  1858.                     push @{ $q->{headers} }, gettext("Requested Hat"), $uhat;
  1859.  
  1860.                     $q->{functions} = [];
  1861.                     push @{ $q->{functions} }, "CMD_ADDHAT";
  1862.                     push @{ $q->{functions} }, "CMD_USEDEFAULT" if $defaulthat;
  1863.                     push @{$q->{functions}}, "CMD_DENY", "CMD_ABORT",
  1864.                       "CMD_FINISHED";
  1865.  
  1866.                     $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ADDHAT" : "CMD_DENY";
  1867.  
  1868.                     $seenevents++;
  1869.  
  1870.                     $ans = UI_PromptUser($q);
  1871.  
  1872.                 }
  1873.                 $transitions{$context} = $ans;
  1874.  
  1875.                 if ($ans eq "CMD_ADDHAT") {
  1876.                     $hat = $uhat;
  1877.                     $sd{$profile}{$hat}{flags} = $sd{$profile}{$profile}{flags};
  1878.                 } elsif ($ans eq "CMD_USEDEFAULT") {
  1879.                     $hat = $defaulthat;
  1880.                 } elsif ($ans eq "CMD_DENY") {
  1881.                     return;
  1882.                 }
  1883.  
  1884.             } elsif ($type eq "capability") {
  1885.                my ($pid, $p, $h, $prog, $sdmode, $capability) = @entry;
  1886.  
  1887.                 if (   ($p !~ /null(-complain)*-profile/)
  1888.                     && ($h !~ /null(-complain)*-profile/))
  1889.                 {
  1890.                     $profile = $p;
  1891.                     $hat     = $h;
  1892.                 }
  1893.  
  1894.                 # print "$pid $profile $hat $prog $sdmode capability $capability\n";
  1895.  
  1896.                 next unless $profile && $hat;
  1897.  
  1898.                 $prelog{$sdmode}{$profile}{$hat}{capability}{$capability} = 1;
  1899.             } elsif (($type eq "path") || ($type eq "exec")) {
  1900.                 my ($pid, $p, $h, $prog, $sdmode, $mode, $detail, $to_name) = @entry;
  1901.  
  1902.         $mode = 0 unless ($mode);
  1903.  
  1904.                 if (   ($p !~ /null(-complain)*-profile/)
  1905.                     && ($h !~ /null(-complain)*-profile/))
  1906.                 {
  1907.                     $profile = $p;
  1908.                     $hat     = $h;
  1909.                 }
  1910.  
  1911.                 next unless $profile && $hat;
  1912.                 my $domainchange = ($type eq "exec") ? "change" : "nochange";
  1913.  
  1914.                 # escape special characters that show up in literal paths
  1915.                 $detail =~ s/(\[|\]|\+|\*|\{|\})/\\$1/g;
  1916.  
  1917.                 # we need to give the Execute dialog if they're requesting x
  1918.                 # access for something that's not a directory - we'll force
  1919.                 # a "ix" Path dialog for directories
  1920.                 my $do_execute  = 0;
  1921.                 my $exec_target = $detail;
  1922.  
  1923.                 if ($mode & str_to_mode("x")) {
  1924.                     if (-d $exec_target) {
  1925.             $mode &= (~$ALL_AA_EXEC_TYPE);
  1926.                         $mode |= str_to_mode("ix");
  1927.                     } else {
  1928.                         $do_execute = 1;
  1929.                     }
  1930.                 }
  1931.  
  1932.         if ($mode & $AA_MAY_LINK) {
  1933.                     if ($detail =~ m/^from (.+) to (.+)$/) {
  1934.                         my ($path, $target) = ($1, $2);
  1935.  
  1936.                         my $frommode = str_to_mode("lr");
  1937.                         if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) {
  1938.                             $frommode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  1939.                         }
  1940.                         $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $frommode;
  1941.  
  1942.                         my $tomode = str_to_mode("lr");
  1943.                         if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$target}) {
  1944.                             $tomode |= $prelog{$sdmode}{$profile}{$hat}{path}{$target};
  1945.                         }
  1946.                         $prelog{$sdmode}{$profile}{$hat}{path}{$target} = $tomode;
  1947.  
  1948.                         # print "$pid $profile $hat $prog $sdmode $path:$frommode -> $target:$tomode\n";
  1949.                     } else {
  1950.                         next;
  1951.                     }
  1952.                 } elsif ($mode) {
  1953.                     my $path = $detail;
  1954.  
  1955.                     if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) {
  1956.                         $mode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  1957.                     }
  1958.                     $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $mode;
  1959.  
  1960.                     # print "$pid $profile $hat $prog $sdmode $mode $path\n";
  1961.                 }
  1962.  
  1963.                 if ($do_execute) {
  1964.                     next if ( profile_known_exec($sd{$profile}{$hat},
  1965.                          "exec", $exec_target ) );
  1966.  
  1967.                     my $p = update_repo_profile($sd{$profile}{$profile});
  1968.  
  1969.             if ($to_name) {
  1970.             next if ( $to_name and
  1971.                   UI_SelectUpdatedRepoProfile($profile, $p) and
  1972.                   profile_known_exec($sd{$profile}{$hat},
  1973.                              "exec", $to_name ) );
  1974.             } else {
  1975.             next if ( UI_SelectUpdatedRepoProfile($profile, $p) and
  1976.                   profile_known_exec($sd{$profile}{$hat},
  1977.                              "exec", $exec_target ) );
  1978.             }
  1979.  
  1980.                     my $context = $profile;
  1981.                     $context .= "^$hat" if $profile ne $hat;
  1982.                     $context .= " -> $exec_target";
  1983.                     my $ans = $transitions{$context} || "";
  1984.  
  1985.                     my ($combinedmode, $combinedaudit, $cm, $am, @m);
  1986.             $combinedmode = 0;
  1987.             $combinedaudit = 0;
  1988.  
  1989.                     # does path match any regexps in original profile?
  1990.                     ($cm, $am, @m) = rematchfrag($sd{$profile}{$hat}, 'allow', $exec_target);
  1991.                     $combinedmode |= $cm if $cm;
  1992.             $combinedaudit |= $am if $am;
  1993.  
  1994.             # find the named transition if is present
  1995.             if ($combinedmode & str_to_mode("x")) {
  1996.             my $nt_name;
  1997.             foreach my $entry (@m) {
  1998.                 if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) {
  1999.                 $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to};
  2000.                 last;
  2001.                 }
  2002.             }
  2003.             if ($to_name and $nt_name and ($to_name ne $nt_name)) {
  2004.                 #fatal_error "transition name from "
  2005.             } elsif ($nt_name) {
  2006.                 $to_name = $nt_name;
  2007.             }
  2008.             }
  2009.  
  2010.                     # does path match anything pulled in by includes in
  2011.                     # original profile?
  2012.                     ($cm, $am, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $exec_target);
  2013.                     $combinedmode |= $cm if $cm;
  2014.                     $combinedaudit |= $am if $am;
  2015.             if ($combinedmode & str_to_mode("x")) {
  2016.             my $nt_name;
  2017.             foreach my $entry (@m) {
  2018.                 if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) {
  2019.                 $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to};
  2020.                 last;
  2021.                 }
  2022.             }
  2023.             if ($to_name and $nt_name and ($to_name ne $nt_name)) {
  2024.                 #fatal_error "transition name from "
  2025.             } elsif ($nt_name) {
  2026.                 $to_name = $nt_name;
  2027.             }
  2028.             }
  2029.  
  2030.  
  2031.             #nx does not exist in profiles.  It does in log
  2032.             #files however.  The log parsing routines will convert
  2033.             #it to its profile form.
  2034.             #nx is internally represented by cx/px/cix/pix + to_name
  2035.                     my $exec_mode = 0;
  2036.             if (contains($combinedmode, "pix")) {
  2037.             if ($to_name) {
  2038.                 $ans = "CMD_nix";
  2039.             } else {
  2040.                 $ans = "CMD_pix";
  2041.             }
  2042.             $exec_mode = str_to_mode("pixr");
  2043.             } elsif (contains($combinedmode, "cix")) {
  2044.             if ($to_name) {
  2045.                 $ans = "CMD_nix";
  2046.             } else {
  2047.                 $ans = "CMD_cix";
  2048.             }
  2049.             $exec_mode = str_to_mode("cixr");
  2050.             } elsif (contains($combinedmode, "Pix")) {
  2051.             if ($to_name) {
  2052.                 $ans = "CMD_nix_safe";
  2053.             } else {
  2054.                 $ans = "CMD_pix_safe";
  2055.             }
  2056.             $exec_mode = str_to_mode("Pixr");
  2057.             } elsif (contains($combinedmode, "Cix")) {
  2058.             if ($to_name) {
  2059.                 $ans = "CMD_nix_safe";
  2060.             } else {
  2061.                 $ans = "CMD_cix_safe";
  2062.             }
  2063.             $exec_mode = str_to_mode("Cixr");
  2064.             } elsif (contains($combinedmode, "ix")) {
  2065.                         $ans       = "CMD_ix";
  2066.                         $exec_mode = str_to_mode("ixr");
  2067.                     } elsif (contains($combinedmode, "px")) {
  2068.             if ($to_name) {
  2069.                 $ans = "CMD_nx";
  2070.             } else {
  2071.                 $ans = "CMD_px";
  2072.             }
  2073.                         $exec_mode = str_to_mode("px");
  2074.             } elsif (contains($combinedmode, "cx")) {
  2075.             if ($to_name) {
  2076.                 $ans = "CMD_nx";
  2077.             } else {
  2078.                 $ans = "CMD_cx";
  2079.             }
  2080.             $exec_mode = str_to_mode("cx");
  2081.                     } elsif (contains($combinedmode, "ux")) {
  2082.                         $ans       = "CMD_ux";
  2083.                         $exec_mode = str_to_mode("ux");
  2084.                     } elsif (contains($combinedmode, "Px")) {
  2085.             if ($to_name) {
  2086.                 $ans = "CMD_nx_safe";
  2087.             } else {
  2088.                 $ans       = "CMD_px_safe";
  2089.             }
  2090.                         $exec_mode = str_to_mode("Px");
  2091.             } elsif (contains($combinedmode, "Cx")) {
  2092.             if ($to_name) {
  2093.                 $ans = "CMD_nx_safe";
  2094.             } else {
  2095.                 $ans = "CMD_cx_safe";
  2096.             }
  2097.             $exec_mode = str_to_mode("Cx");
  2098.                     } elsif (contains($combinedmode, "Ux")) {
  2099.                         $ans       = "CMD_ux_safe";
  2100.                         $exec_mode = str_to_mode("Ux");
  2101.                     } else {
  2102.                         my $options = $cfg->{qualifiers}{$exec_target} || "ipcnu";
  2103.             fatal_error "$entry has transition name but not transition mode" if $to_name;
  2104.  
  2105.                         # force "ix" as the only option when the profiled
  2106.                         # program executes itself
  2107.                         $options = "i" if $exec_target eq $profile;
  2108.  
  2109.             # for now don't allow hats to cx
  2110.             $options =~ s/c// if $hat and $hat ne $profile;
  2111.  
  2112.                         # we always need deny...
  2113.                         $options .= "d";
  2114.  
  2115.                         # figure out what our default option should be...
  2116.                         my $default;
  2117.                         if ($options =~ /p/
  2118.                             && -e getprofilefilename($exec_target))
  2119.                         {
  2120.                             $default = "CMD_px";
  2121.                         } elsif ($options =~ /i/) {
  2122.                             $default = "CMD_ix";
  2123.                         } elsif ($options =~ /c/) {
  2124.                             $default = "CMD_cx";
  2125.                         } elsif ($options =~ /n/) {
  2126.                             $default = "CMD_nx";
  2127.                         } else {
  2128.                             $default = "CMD_DENY";
  2129.                         }
  2130.  
  2131.                         # ugh, this doesn't work if someone does an ix before
  2132.                         # calling this particular child process.  at least
  2133.                         # it's only a hint instead of mandatory to get this
  2134.                         # right.
  2135.                         my $parent_uses_ld_xxx = check_for_LD_XXX($profile);
  2136.  
  2137.                         my $severity = $sevdb->rank($exec_target, "x");
  2138.  
  2139.                         # build up the prompt...
  2140.                         my $q = {};
  2141.                         $q->{headers} = [];
  2142.                         push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  2143.                         if ($prog && $prog ne "HINT") {
  2144.                             push @{ $q->{headers} }, gettext("Program"), $prog;
  2145.                         }
  2146.             # $to_name should NOT exist here other wise we know what
  2147.             # mode we are supposed to be transitioning to
  2148.             # which is handled above.
  2149.                         push @{ $q->{headers} }, gettext("Execute"),  $exec_target;
  2150.                         push @{ $q->{headers} }, gettext("Severity"), $severity;
  2151.  
  2152.                         $q->{functions} = [];
  2153.  
  2154.                         my $prompt = "\n$context\n";
  2155.             my $exec_toggle = 0;
  2156.  
  2157.             push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle);
  2158.  
  2159.                         $options = join("|", split(//, $options));
  2160.  
  2161.                         $seenevents++;
  2162.  
  2163.             while ($ans !~ m/^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$/) {
  2164.                 $ans = UI_PromptUser($q);
  2165.  
  2166.                 if ($ans =~ /CMD_EXEC_IX_/) {
  2167.                 $exec_toggle = !$exec_toggle;
  2168.  
  2169.                 $q->{functions} = [ ];
  2170.                 push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle);
  2171.                 $ans = "";
  2172.                 next;
  2173.                 }
  2174.                 if ($ans =~ /CMD_(nx|nix)/) {
  2175.                                 my $arg = $exec_target;
  2176.  
  2177.                 my $ynans = "n";
  2178.                 if ($profile eq $hat) {
  2179.                     $ynans = UI_YesNo("Are you specifying a transition to a local profile?", "n");
  2180.                 }
  2181.  
  2182.                 if ($ynans eq "y") {
  2183.                     if ($ans eq "CMD_nx") {
  2184.                     $ans = "CMD_cx";
  2185.                     } else {
  2186.                     $ans = "CMD_cix";
  2187.                     }
  2188.                 } else {
  2189.                     if ($ans eq "CMD_nx") {
  2190.                     $ans = "CMD_px";
  2191.                     } else {
  2192.                     $ans = "CMD_pix";
  2193.                     }
  2194.                 }
  2195.                 $to_name = UI_GetString(gettext("Enter profile name to transition to: "), $arg);
  2196.                 }
  2197.                 if ($ans =~ /CMD_ix/) {
  2198.                 $exec_mode = str_to_mode("ix");
  2199.                             } elsif ($ans =~ /CMD_(px|cx|nx|pix|cix|nix)/) {
  2200.                 my $match = $1;
  2201.                 $exec_mode = str_to_mode($match);
  2202.                                 my $px_default = "n";
  2203.                                 my $px_mesg    = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.");
  2204.                                 if ($parent_uses_ld_xxx) {
  2205.                                     $px_mesg = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut this application appears to use LD_PRELOAD\nor LD_LIBRARY_PATH and clearing these could\ncause functionality problems.");
  2206.                                 }
  2207.                                 my $ynans = UI_YesNo($px_mesg, $px_default);
  2208.                 $ans = "CMD_$match";
  2209.                                 if ($ynans eq "y") {
  2210.                                     $exec_mode &= ~$AA_EXEC_UNSAFE;
  2211.                                 }
  2212.                             } elsif ($ans eq "CMD_ux") {
  2213.                 $exec_mode = str_to_mode("ux");
  2214.                                 my $ynans = UI_YesNo(sprintf(gettext("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing \%s?"), $exec_target), "n");
  2215.                                 if ($ynans eq "y") {
  2216.                                     my $ynans = UI_YesNo(gettext("Should AppArmor sanitize the environment when\nrunning this program unconfined?\n\nNot sanitizing the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), "y");
  2217.                                     if ($ynans eq "y") {
  2218.                     $exec_mode &= ~($AA_EXEC_UNSAFE | ($AA_EXEC_UNSAFE << $AA_OTHER_SHIFT));
  2219.                                     }
  2220.                                 } else {
  2221.                                     $ans = "INVALID";
  2222.                                 }
  2223.                             }
  2224.                         }
  2225.                         $transitions{$context} = $ans;
  2226.  
  2227.             if ($ans =~ /CMD_(ix|px|cx|nx|pix|cix|nix)/) {
  2228.                 # if we're inheriting, things'll bitch unless we have r
  2229.                 if ($exec_mode & str_to_mode("i")) {
  2230.                 $exec_mode |= str_to_mode("r");
  2231.                 }
  2232.  
  2233.             } else {
  2234.                 if ($ans eq "CMD_DENY") {
  2235.                 $sd{$profile}{$hat}{deny}{path}{$exec_target}{mode} |= str_to_mode("x");
  2236.  
  2237.                 $sd{$profile}{$hat}{deny}{path}{$exec_target}{audit} |= 0;
  2238.                 $changed{$profile} = 1;
  2239.                                 # skip all remaining events if they say to deny
  2240.                                 # the exec
  2241.                                 return if $domainchange eq "change";
  2242.                 }
  2243.  
  2244.                         }
  2245.  
  2246.             unless ($ans eq "CMD_DENY") {
  2247. # ???? if its defined in the prelog we shouldn't have asked
  2248.                             if (defined $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target}) {
  2249. #                                $exec_mode = $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target};
  2250.                             }
  2251.  
  2252.                             $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target} |= $exec_mode;
  2253.                             $log{PERMITTING}{$profile}              = {};
  2254.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{mode} |= $exec_mode;
  2255.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{audit} |= 0;
  2256.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{to} = $to_name if ($to_name);
  2257.  
  2258.                             # mark this profile as changed
  2259.                             $changed{$profile} = 1;
  2260.  
  2261.                             if ($exec_mode & str_to_mode("i")) {
  2262.                                 if ($exec_target =~ /perl/) {
  2263.                                     $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1;
  2264.                                 } elsif ($detail =~ m/\/bin\/(bash|sh)/) {
  2265.                                     $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1;
  2266.                                 }
  2267.                                 my $hashbang = head($exec_target);
  2268.                                 if ($hashbang =~ /^#!\s*(\S+)/) {
  2269.                                     my $interpreter = get_full_path($1);
  2270.                                     $sd{$profile}{$hat}{path}->{$interpreter}{mode} |= str_to_mode("ix");
  2271.                                     $sd{$profile}{$hat}{path}->{$interpreter}{audit} |= 0;
  2272.                                     if ($interpreter =~ /perl/) {
  2273.                                         $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1;
  2274.                                     } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
  2275.                                         $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1;
  2276.                                     }
  2277.                                 }
  2278.                             }
  2279.                         }
  2280.             }
  2281.  
  2282.                     # print "$pid $profile $hat EXEC $exec_target $ans $exec_mode\n";
  2283.  
  2284.                     # update our tracking info based on what kind of change
  2285.                     # this is...
  2286.                     if ($ans eq "CMD_ix") {
  2287.             if ($hat) {
  2288.                 $profilechanges{$pid} = $profile . "//" . $hat;
  2289.             } else {
  2290.                 $profilechanges{$pid} = $profile;
  2291.             }
  2292.                     } elsif ($ans =~ /^CMD_(px|nx|pix|nix)/) {
  2293.             $exec_target = $to_name if ($to_name);
  2294.                         if ($sdmode eq "PERMITTING") {
  2295.                             if ($domainchange eq "change") {
  2296.                                 $profile              = $exec_target;
  2297.                                 $hat                  = $exec_target;
  2298.                                 $profilechanges{$pid} = $profile;
  2299.                             }
  2300.                         }
  2301.                         # if they want to use px, make sure a profile
  2302.                         # exists for the target.
  2303.                         unless (-e getprofilefilename($exec_target)) {
  2304.                 my $ynans = "y";
  2305.                 if ($exec_mode & str_to_mode("i")) {
  2306.                 $ynans = UI_YesNo(sprintf(gettext("A profile for %s does not exist create one?"), $exec_target), "n");
  2307.                 }
  2308.                 if ($ynans eq "y") {
  2309.                 $helpers{$exec_target} = "enforce";
  2310.                 if ($to_name) {
  2311.                     autodep_base("", $exec_target);
  2312.                 } else {
  2313.                     autodep_base($exec_target, "");
  2314.                 }
  2315.                 reload_base($exec_target);
  2316.                 }
  2317.                         }
  2318.                     } elsif ($ans =~ /^CMD_(cx|cix)/) {
  2319.             $exec_target = $to_name if ($to_name);
  2320.                         if ($sdmode eq "PERMITTING") {
  2321.                             if ($domainchange eq "change") {
  2322.                                 $profilechanges{$pid} = "${profile}//${exec_target}";
  2323. #                                $profile              = $exec_target;
  2324. #                                $hat                  = $exec_target;
  2325.                             }
  2326.                         }
  2327.  
  2328.                         # if they want to use cx, make sure a profile
  2329.                         # exists for the target.
  2330.             unless ($sd{$profile}{$exec_target}) {
  2331.                 my $ynans = "y";
  2332.                 if ($exec_mode & str_to_mode("i")) {
  2333.                 $ynans = UI_YesNo(sprintf(gettext("A local profile for %s does not exist create one?"), $exec_target), "n");
  2334.                 }
  2335.                 if ($ynans eq "y") {
  2336.                 $hat = $exec_target;
  2337.                 # keep track of profile flags
  2338.                 #$profile_data->{$profile}{$hat}{flags} = ;
  2339.  
  2340.                 # we have seen more than a declaration so clear it
  2341.                 $sd{$profile}{$hat}{'declared'} = 0;
  2342.                 $sd{$profile}{$hat}{profile} = 1;
  2343.                 $sd{$profile}{$hat}{allow}{path} = { };
  2344.                 $sd{$profile}{$hat}{allow}{netdomain} = { };
  2345.                 my $file = $sd{$profile}{$profile}{filename};
  2346.                 $filelist{$file}{profiles}{$profile}{$hat} = 1;
  2347.  
  2348.                 }
  2349.                         }
  2350.                     } elsif ($ans =~ /^CMD_ux/) {
  2351.                         $profilechanges{$pid} = "unconfined";
  2352.                         return if $domainchange eq "change";
  2353.                     }
  2354.                 }
  2355.             } elsif ( $type eq "netdomain" ) {
  2356.                my ($pid, $p, $h, $prog, $sdmode, $family, $sock_type, $protocol) =
  2357.                   @entry;
  2358.  
  2359.                 if (   ($p !~ /null(-complain)*-profile/)
  2360.                     && ($h !~ /null(-complain)*-profile/))
  2361.                 {
  2362.                     $profile = $p;
  2363.                     $hat     = $h;
  2364.                 }
  2365.  
  2366.                 next unless $profile && $hat;
  2367.                 $prelog{$sdmode}
  2368.                        {$profile}
  2369.                        {$hat}
  2370.                        {netdomain}
  2371.                        {$family}
  2372.                        {$sock_type} = 1 unless ( !$family || !$sock_type );
  2373.  
  2374.             }
  2375.         }
  2376.     }
  2377. }
  2378.  
  2379. sub add_to_tree ($@) {
  2380.     my ($pid, $parent, $type, @event) = @_;
  2381.     if ( $DEBUGGING ) {
  2382.         my $eventmsg = Data::Dumper->Dump([@event], [qw(*event)]);
  2383.         $eventmsg =~ s/\n/ /g;
  2384.         debug " add_to_tree: pid [$pid] type [$type] event [ $eventmsg ]";
  2385.     }
  2386.  
  2387.     unless (exists $pid{$pid}) {
  2388.     my $profile = $event[0];
  2389.     my $hat = $event[1];
  2390.     if ($parent && exists $pid{$parent}) {
  2391.         # fork entry is missing fake one so that fork tracking will work
  2392.         $hat     ||= "null-complain-profile";
  2393.         my $arrayref = [];
  2394.             push @{ $pid{$parent} }, $arrayref;
  2395.         $pid{$pid} = $arrayref;
  2396.         push @{$arrayref}, [ "fork", $pid, $profile, $hat ];
  2397.     } else {
  2398.         my $arrayref = [];
  2399.         push @log, $arrayref;
  2400.         $pid{$pid} = $arrayref;
  2401.     }
  2402.     }
  2403.  
  2404.     push @{ $pid{$pid} }, [ $type, $pid, @event ];
  2405. }
  2406.  
  2407. #
  2408. # variables used in the logparsing routines
  2409. #
  2410. our $LOG;
  2411. our $next_log_entry;
  2412. our $logmark;
  2413. our $seenmark;
  2414. my $RE_LOG_v2_0_syslog = qr/SubDomain/;
  2415. my $RE_LOG_v2_1_syslog = qr/kernel:\s+(\[[\d\.\s]+\]\s+)?(audit\([\d\.\:]+\):\s+)?type=150[1-6]/;
  2416. my $RE_LOG_v2_0_audit  =
  2417.     qr/type=(APPARMOR|UNKNOWN\[1500\]) msg=audit\([\d\.\:]+\):/;
  2418. my $RE_LOG_v2_1_audit  =
  2419.     qr/type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))/;
  2420.  
  2421. sub prefetch_next_log_entry {
  2422.     # if we already have an existing cache entry, something's broken
  2423.     if ($next_log_entry) {
  2424.         print STDERR "Already had next log entry: $next_log_entry";
  2425.     }
  2426.  
  2427.     # read log entries until we either hit the end or run into an
  2428.     # AA event message format we recognize
  2429.     do {
  2430.         $next_log_entry = <$LOG>;
  2431.         $DEBUGGING && debug "prefetch_next_log_entry: next_log_entry = " . ($next_log_entry ? $next_log_entry : "empty");
  2432.     } until (!$next_log_entry || $next_log_entry =~ m{
  2433.         $RE_LOG_v2_0_syslog |
  2434.         $RE_LOG_v2_0_audit  |
  2435.         $RE_LOG_v2_1_audit  |
  2436.         $RE_LOG_v2_1_syslog |
  2437.         $logmark
  2438.     }x);
  2439. }
  2440.  
  2441. sub get_next_log_entry {
  2442.     # make sure we've got a next log entry if there is one
  2443.     prefetch_next_log_entry() unless $next_log_entry;
  2444.  
  2445.     # save a copy of the next log entry...
  2446.     my $log_entry = $next_log_entry;
  2447.  
  2448.     # zero out our cache of the next log entry
  2449.     $next_log_entry = undef;
  2450.  
  2451.     return $log_entry;
  2452. }
  2453.  
  2454. sub peek_at_next_log_entry {
  2455.     # make sure we've got a next log entry if there is one
  2456.     prefetch_next_log_entry() unless $next_log_entry;
  2457.  
  2458.     # return a copy of the next log entry without pulling it out of the cache
  2459.     return $next_log_entry;
  2460. }
  2461.  
  2462. sub throw_away_next_log_entry {
  2463.     $next_log_entry = undef;
  2464. }
  2465.  
  2466. sub parse_log_record_v_2_0 ($@) {
  2467.     my ($record, $last) = @_;
  2468.     $DEBUGGING && debug "parse_log_record_v_2_0: $record";
  2469.  
  2470.     # What's this early out for?  As far as I can tell, parse_log_record_v_2_0
  2471.     # won't ever be called without something in $record
  2472.     return $last if ( ! $record );
  2473.  
  2474.     $_ = $record;
  2475.  
  2476.     if (s/(PERMITTING|REJECTING)-SYSLOGFIX/$1/) {
  2477.         s/%%/%/g;
  2478.     }
  2479.  
  2480.     if (m/LOGPROF-HINT unknown_hat (\S+) pid=(\d+) profile=(.+) active=(.+)/) {
  2481.         my ($uhat, $pid, $profile, $hat) = ($1, $2, $3, $4);
  2482.  
  2483.         $last = $&;
  2484.  
  2485.         # we want to ignore entries for profiles that don't exist
  2486.         # they're most likely broken entries or old entries for
  2487.         # deleted profiles
  2488.         return $&
  2489.           if ( ($profile ne 'null-complain-profile')
  2490.             && (!profile_exists($profile)));
  2491.  
  2492.         add_to_tree($pid, 0, "unknown_hat", $profile, $hat,
  2493.                     "PERMITTING", $uhat);
  2494.     } elsif (m/LOGPROF-HINT (unknown_profile|missing_mandatory_profile) image=(.+) pid=(\d+) profile=(.+) active=(.+)/) {
  2495.         my ($image, $pid, $profile, $hat) = ($2, $3, $4, $5);
  2496.  
  2497.         return $& if $last =~ /PERMITTING x access to $image/;
  2498.         $last = $&;
  2499.  
  2500.         # we want to ignore entries for profiles that don't exist
  2501.         # they're most likely broken entries or old entries for
  2502.         # deleted profiles
  2503.         return $&
  2504.           if ( ($profile ne 'null-complain-profile')
  2505.             && (!profile_exists($profile)));
  2506.  
  2507.         add_to_tree($pid, 0, "exec", $profile, $hat, "HINT", "PERMITTING", "x", $image);
  2508.  
  2509.     } elsif (m/(PERMITTING|REJECTING) (\S+) access (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2510.         my ($sdmode, $mode, $detail, $prog, $pid, $profile, $hat) =
  2511.            ($1, $2, $3, $4, $5, $6, $7);
  2512.  
  2513.     if ($mode eq "link") {
  2514.         $mode = "l";
  2515.     }
  2516.         if (!validate_log_mode($mode)) {
  2517.             fatal_error(sprintf(gettext('Log contains unknown mode %s.'), $mode));
  2518.         }
  2519.  
  2520.         my $domainchange = "nochange";
  2521.         if ($mode =~ /x/) {
  2522.  
  2523.             # we need to try to check if we're doing a domain transition
  2524.             if ($sdmode eq "PERMITTING") {
  2525.                 my $following = peek_at_next_log_entry();
  2526.  
  2527.                 if ($following && ($following =~ m/changing_profile/)) {
  2528.                     $domainchange = "change";
  2529.                     throw_away_next_log_entry();
  2530.                 }
  2531.             }
  2532.         } else {
  2533.  
  2534.             # we want to ignore duplicates for things other than executes...
  2535.             return $& if $seen{$&};
  2536.             $seen{$&} = 1;
  2537.         }
  2538.  
  2539.         $last = $&;
  2540.  
  2541.         # we want to ignore entries for profiles that don't exist
  2542.         # they're most likely broken entries or old entries for
  2543.         # deleted profiles
  2544.         if (($profile ne 'null-complain-profile')
  2545.             && (!profile_exists($profile)))
  2546.         {
  2547.             return $&;
  2548.         }
  2549.  
  2550.         # currently no way to stick pipe mediation in a profile, ignore
  2551.         # any messages like this
  2552.         return $& if $detail =~ /to pipe:/;
  2553.  
  2554.         # strip out extra extended attribute info since we don't
  2555.         # currently have a way to specify it in the profile and
  2556.         # instead just need to provide the access to the base filename
  2557.         $detail =~ s/\s+extended attribute \S+//;
  2558.  
  2559.         # kerberos code checks to see if the krb5.conf file is world
  2560.         # writable in a stupid way so we'll ignore any w accesses to
  2561.         # krb5.conf
  2562.         return $& if (($detail eq "to /etc/krb5.conf") && contains($mode, "w"));
  2563.  
  2564.         # strip off the (deleted) tag that gets added if it's a
  2565.         # deleted file
  2566.         $detail =~ s/\s+\(deleted\)$//;
  2567.  
  2568.     #            next if (($detail =~ /to \/lib\/ld-/) && ($mode =~ /x/));
  2569.  
  2570.         $detail =~ s/^to\s+//;
  2571.  
  2572.         if ($domainchange eq "change") {
  2573.             add_to_tree($pid, 0, "exec", $profile, $hat, $prog,
  2574.                         $sdmode, str_to_mode($mode), $detail);
  2575.         } else {
  2576.             add_to_tree($pid, 0, "path", $profile, $hat, $prog,
  2577.                         $sdmode, str_to_mode($mode), $detail);
  2578.         }
  2579.  
  2580.     } elsif (m/(PERMITTING|REJECTING) (?:mk|rm)dir on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2581.         my ($sdmode, $path, $prog, $pid, $profile, $hat) =
  2582.            ($1, $2, $3, $4, $5, $6);
  2583.  
  2584.         # we want to ignore duplicates for things other than executes...
  2585.         return $& if $seen{$&}++;
  2586.  
  2587.         $last = $&;
  2588.  
  2589.         # we want to ignore entries for profiles that don't exist
  2590.         # they're most likely broken entries or old entries for
  2591.         # deleted profiles
  2592.         return $&
  2593.           if ( ($profile ne 'null-complain-profile')
  2594.             && (!profile_exists($profile)));
  2595.  
  2596.         add_to_tree($pid, 0, "path", $profile, $hat, $prog, $sdmode,
  2597.                     "w", $path);
  2598.  
  2599.     } elsif (m/(PERMITTING|REJECTING) xattr (\S+) on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2600.         my ($sdmode, $xattr_op, $path, $prog, $pid, $profile, $hat) =
  2601.            ($1, $2, $3, $4, $5, $6, $7);
  2602.  
  2603.         # we want to ignore duplicates for things other than executes...
  2604.         return $& if $seen{$&}++;
  2605.  
  2606.         $last = $&;
  2607.  
  2608.         # we want to ignore entries for profiles that don't exist
  2609.         # they're most likely broken entries or old entries for
  2610.         # deleted profiles
  2611.         return $&
  2612.           if ( ($profile ne 'null-complain-profile')
  2613.             && (!profile_exists($profile)));
  2614.  
  2615.         my $xattrmode;
  2616.         if ($xattr_op eq "get" || $xattr_op eq "list") {
  2617.             $xattrmode = "r";
  2618.         } elsif ($xattr_op eq "set" || $xattr_op eq "remove") {
  2619.             $xattrmode = "w";
  2620.         }
  2621.  
  2622.         if ($xattrmode) {
  2623.             add_to_tree($pid, 0, "path", $profile, $hat, $prog, $sdmode,
  2624.                         str_to_mode($xattrmode), $path);
  2625.         }
  2626.  
  2627.     } elsif (m/(PERMITTING|REJECTING) attribute \((.*?)\) change to (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2628.         my ($sdmode, $change, $path, $prog, $pid, $profile, $hat) =
  2629.            ($1, $2, $3, $4, $5, $6, $7);
  2630.  
  2631.         # we want to ignore duplicates for things other than executes...
  2632.         return $& if $seen{$&};
  2633.         $seen{$&} = 1;
  2634.  
  2635.         $last = $&;
  2636.  
  2637.         # we want to ignore entries for profiles that don't exist
  2638.         # they're most likely broken entries or old entries for
  2639.         # deleted profiles
  2640.         return $&
  2641.           if ( ($profile ne 'null-complain-profile')
  2642.             && (!profile_exists($profile)));
  2643.  
  2644.         # kerberos code checks to see if the krb5.conf file is world
  2645.         # writable in a stupid way so we'll ignore any w accesses to
  2646.         # krb5.conf
  2647.         return $& if $path eq "/etc/krb5.conf";
  2648.  
  2649.         add_to_tree($pid, 0, "path", $profile, $hat, $prog, $sdmode,
  2650.                     str_to_mode("w"), $path);
  2651.  
  2652.     } elsif (m/(PERMITTING|REJECTING) access to capability '(\S+)' \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2653.         my ($sdmode, $capability, $prog, $pid, $profile, $hat) =
  2654.            ($1, $2, $3, $4, $5, $6);
  2655.  
  2656.         return $& if $seen{$&};
  2657.  
  2658.         $seen{$&} = 1;
  2659.         $last = $&;
  2660.  
  2661.         # we want to ignore entries for profiles that don't exist - they're
  2662.         # most likely broken entries or old entries for deleted profiles
  2663.         return $&
  2664.           if ( ($profile ne 'null-complain-profile')
  2665.             && (!profile_exists($profile)));
  2666.  
  2667.         add_to_tree($pid, 0, "capability", $profile, $hat, $prog,
  2668.                     $sdmode, $capability);
  2669.  
  2670.     } elsif (m/Fork parent (\d+) child (\d+) profile (.+) active (.+)/
  2671.         || m/LOGPROF-HINT fork pid=(\d+) child=(\d+) profile=(.+) active=(.+)/
  2672.         || m/LOGPROF-HINT fork pid=(\d+) child=(\d+)/)
  2673.     {
  2674.         my ($parent, $child, $profile, $hat) = ($1, $2, $3, $4);
  2675.  
  2676.         $profile ||= "null-complain-profile";
  2677.         $hat     ||= "null-complain-profile";
  2678.  
  2679.         $last = $&;
  2680.  
  2681.         # we want to ignore entries for profiles that don't exist
  2682.         # they're  most likely broken entries or old entries for
  2683.         # deleted profiles
  2684.         return $&
  2685.           if ( ($profile ne 'null-complain-profile')
  2686.             && (!profile_exists($profile)));
  2687.  
  2688.         my $arrayref = [];
  2689.         if (exists $pid{$parent}) {
  2690.             push @{ $pid{$parent} }, $arrayref;
  2691.         } else {
  2692.             push @log, $arrayref;
  2693.         }
  2694.         $pid{$child} = $arrayref;
  2695.         push @{$arrayref}, [ "fork", $child, $profile, $hat ];
  2696.     } else {
  2697.         $DEBUGGING && debug "UNHANDLED: $_";
  2698.     }
  2699.     return $last;
  2700. }
  2701.  
  2702. sub parse_log_record ($) {
  2703.     my $record = shift;
  2704.     $DEBUGGING && debug "parse_log_record: $record";
  2705.     my $e = parse_event($record);
  2706.  
  2707.     return $e;
  2708. }
  2709.  
  2710.  
  2711. sub add_event_to_tree ($) {
  2712.     my $e = shift;
  2713.  
  2714.     my $sdmode = $e->{sdmode}?$e->{sdmode}:"UNKNOWN";
  2715.     if ( $e->{type} ) {
  2716.         if ( $e->{type} =~ /(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)/ ) {
  2717.             $sdmode = "AUDIT";
  2718.         } elsif ( $e->{type} =~ /(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)/ ) {
  2719.             $sdmode = "PERMITTING";
  2720.         } elsif ( $e->{type} =~ /(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)/ ) {
  2721.             $sdmode = "REJECTING";
  2722.         } elsif ( $e->{type} =~ /(UNKNOWN\[1504\]|APPARMOR_HINT|1504)/ ) {
  2723.             $sdmode = "HINT";
  2724.         } elsif ( $e->{type} =~ /(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)/ ) {
  2725.             $sdmode = "STATUS";
  2726.         } elsif ( $e->{type} =~ /(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)/ ) {
  2727.             $sdmode = "ERROR";
  2728.         } else {
  2729.             $sdmode = "UNKNOWN";
  2730.         }
  2731.     }
  2732.     return if ( $sdmode =~ /UNKNOWN|AUDIT|STATUS|ERROR/ );
  2733.     return if ($e->{operation} =~ /profile_set/);
  2734.  
  2735.     my ($profile, $hat);
  2736.     # just convert new null profile style names to old before we begin processing
  2737.     # profile and name can contain multiple layers of null- but all we care about
  2738.     # currently is single level.
  2739.     if ($e->{profile} =~ m/\/\/null-/) {
  2740.         $e->{profile} = "null-complain-profile";
  2741.     }
  2742.     ($profile, $hat) = split /\/\//, $e->{profile};
  2743.     if ( $e->{operation} eq "change_hat" ) {
  2744.     #screen out change_hat events that aren't part of learning, as before
  2745.     #AppArmor 2.4 these events only happend as hints during learning
  2746.     return if ($sdmode ne "HINT" &&  $sdmode ne "PERMITTING");
  2747.         ($profile, $hat) = split /\/\//, $e->{name};
  2748.     }
  2749.     $hat = $profile if ( !$hat );
  2750.     # TODO - refactor add_to_tree as prog is no longer supplied
  2751.     #        HINT is from previous format where prog was not
  2752.     #        consistently passed
  2753.     my $prog = "HINT";
  2754.  
  2755.     return if ($profile ne 'null-complain-profile' && !profile_exists($profile));
  2756.  
  2757.     if ($e->{operation} eq "exec") {
  2758.         if ( defined $e->{info} && $e->{info} eq "mandatory profile missing" ) {
  2759.             add_to_tree( $e->{pid},
  2760.              $e->{parent},
  2761.                          "exec",
  2762.                          $profile,
  2763.                          $hat,
  2764.                          $sdmode,
  2765.                          "PERMITTING",
  2766.                          $e->{denied_mask},
  2767.                          $e->{name},
  2768.                          $e->{name2}
  2769.                        );
  2770.         } elsif ( defined $e->{name2} && $e->{name2} =~ m/\/\/null-/) {
  2771.             add_to_tree( $e->{pid},
  2772.              $e->{parent},
  2773.                           "exec",
  2774.                           $profile,
  2775.                           $hat,
  2776.                           $prog,
  2777.                           $sdmode,
  2778.                           $e->{denied_mask},
  2779.                           $e->{name},
  2780.               ""
  2781.                         );
  2782.         }
  2783.     } elsif ($e->{operation} =~ m/file_/) {
  2784.         add_to_tree( $e->{pid},
  2785.              $e->{parent},
  2786.                      "path",
  2787.                      $profile,
  2788.                      $hat,
  2789.                      $prog,
  2790.                      $sdmode,
  2791.                      $e->{denied_mask},
  2792.                      $e->{name},
  2793.              "",
  2794.                    );
  2795.     } elsif ($e->{operation} eq "open") {
  2796.         add_to_tree( $e->{pid},
  2797.              $e->{parent},
  2798.                      "path",
  2799.                      $profile,
  2800.                      $hat,
  2801.                      $prog,
  2802.                      $sdmode,
  2803.                      $e->{denied_mask},
  2804.                      $e->{name},
  2805.              "",
  2806.                    );
  2807.     } elsif ($e->{operation} eq "capable") {
  2808.         add_to_tree( $e->{pid},
  2809.              $e->{parent},
  2810.                      "capability",
  2811.                      $profile,
  2812.                      $hat,
  2813.                      $prog,
  2814.                      $sdmode,
  2815.                      $e->{name}
  2816.                    );
  2817.     } elsif ($e->{operation} =~  m/xattr/ ||
  2818.              $e->{operation} eq "setattr") {
  2819.         add_to_tree( $e->{pid},
  2820.              $e->{parent},
  2821.                      "path",
  2822.                      $profile,
  2823.                      $hat,
  2824.                      $prog,
  2825.                      $sdmode,
  2826.                      $e->{denied_mask},
  2827.                      $e->{name},
  2828.              ""
  2829.                     );
  2830.     } elsif ($e->{operation} =~ m/inode_/) {
  2831.         my $is_domain_change = 0;
  2832.  
  2833.         if ($e->{operation}   eq "inode_permission" &&
  2834.             $e->{denied_mask} & $AA_MAY_EXEC                &&
  2835.             $sdmode           eq "PERMITTING") {
  2836.  
  2837.             my $following = peek_at_next_log_entry();
  2838.             if ($following) {
  2839.                 my $entry = parse_log_record($following);
  2840.                 if ($entry &&
  2841.                     $entry->{info} &&
  2842.                     $entry->{info} eq "set profile" ) {
  2843.  
  2844.                     $is_domain_change = 1;
  2845.                     throw_away_next_log_entry();
  2846.                 }
  2847.             }
  2848.         }
  2849.  
  2850.         if ($is_domain_change) {
  2851.             add_to_tree( $e->{pid},
  2852.              $e->{parent},
  2853.                           "exec",
  2854.                           $profile,
  2855.                           $hat,
  2856.                           $prog,
  2857.                           $sdmode,
  2858.                           $e->{denied_mask},
  2859.                           $e->{name},
  2860.               $e->{name2}
  2861.                         );
  2862.         } else {
  2863.              add_to_tree( $e->{pid},
  2864.               $e->{parent},
  2865.                           "path",
  2866.                           $profile,
  2867.                           $hat,
  2868.                           $prog,
  2869.                           $sdmode,
  2870.                           $e->{denied_mask},
  2871.                           $e->{name},
  2872.               ""
  2873.                         );
  2874.         }
  2875.     } elsif ($e->{operation} eq "sysctl") {
  2876.         add_to_tree( $e->{pid},
  2877.              $e->{parent},
  2878.                      "path",
  2879.                      $profile,
  2880.                      $hat,
  2881.                      $prog,
  2882.                      $sdmode,
  2883.                      $e->{denied_mask},
  2884.                      $e->{name},
  2885.              ""
  2886.                    );
  2887.     } elsif ($e->{operation} eq "clone") {
  2888.         my ($parent, $child)  = ($e->{pid}, $e->{task});
  2889.         $profile ||= "null-complain-profile";
  2890.         $hat     ||= "null-complain-profile";
  2891.         my $arrayref = [];
  2892.         if (exists $pid{$parent}) {
  2893.             push @{ $pid{$parent} }, $arrayref;
  2894.         } else {
  2895.             push @log, $arrayref;
  2896.         }
  2897.         $pid{$child} = $arrayref;
  2898.         push @{$arrayref}, [ "fork", $child, $profile, $hat ];
  2899.     } elsif ($e->{operation} =~ m/socket_/) {
  2900.         add_to_tree( $e->{pid},
  2901.              $e->{parent},
  2902.                      "netdomain",
  2903.                      $profile,
  2904.                      $hat,
  2905.                      $prog,
  2906.                      $sdmode,
  2907.                      $e->{family},
  2908.                      $e->{sock_type},
  2909.                      $e->{protocol},
  2910.                    );
  2911.     } elsif ($e->{operation} eq "change_hat") {
  2912.         add_to_tree($e->{pid}, $e->{parent}, "unknown_hat", $profile, $hat, $sdmode, $hat);
  2913.     } else {
  2914.         if ( $DEBUGGING ) {
  2915.             my $msg = Data::Dumper->Dump([$e], [qw(*event)]);
  2916.             debug "UNHANDLED: $msg";
  2917.         }
  2918.     }
  2919. }
  2920.  
  2921. sub read_log {
  2922.     $logmark = shift;
  2923.     $seenmark = $logmark ? 0 : 1;
  2924.     my $last;
  2925.     my $event_type;
  2926.  
  2927.     # okay, done loading the previous profiles, get on to the good stuff...
  2928.     open($LOG, $filename)
  2929.       or fatal_error "Can't read AppArmor logfile $filename: $!";
  2930.     while ($_ = get_next_log_entry()) {
  2931.         chomp;
  2932.  
  2933.     $DEBUGGING && debug "read_log: $_";
  2934.  
  2935.         $seenmark = 1 if /$logmark/;
  2936.  
  2937.     $DEBUGGING && debug "read_log: seenmark = $seenmark";
  2938.         next unless $seenmark;
  2939.  
  2940.         my $last_match = ""; # v_2_0 syslog record parsing requires
  2941.                              # the previous aa record in the mandatory profile
  2942.                              # case
  2943.         # all we care about is apparmor messages
  2944.         if (/$RE_LOG_v2_0_syslog/ || /$RE_LOG_v2_0_audit/) {
  2945.            $last_match = parse_log_record_v_2_0( $_, $last_match );
  2946.         } else {
  2947.             my $event = parse_log_record($_);
  2948.             add_event_to_tree($event) if ( $event );
  2949.         }
  2950.     }
  2951.     close($LOG);
  2952.     $logmark = "";
  2953. }
  2954.  
  2955.  
  2956. sub UI_SelectUpdatedRepoProfile ($$) {
  2957.  
  2958.     my ($profile, $p) = @_;
  2959.     my $distro        = $cfg->{repository}{distro};
  2960.     my $url           = $sd{$profile}{$profile}{repo}{url};
  2961.     my $user          = $sd{$profile}{$profile}{repo}{user};
  2962.     my $id            = $sd{$profile}{$profile}{repo}{id};
  2963.     my $updated       = 0;
  2964.  
  2965.     if ($p) {
  2966.         my $q = { };
  2967.         $q->{headers} = [
  2968.           "Profile", $profile,
  2969.           "User", $user,
  2970.           "Old Revision", $id,
  2971.           "New Revision", $p->{id},
  2972.         ];
  2973.         $q->{explanation} =
  2974.           gettext( "An updated version of this profile has been found in the profile repository.  Would you like to use it?");
  2975.         $q->{functions} = [
  2976.           "CMD_VIEW_CHANGES", "CMD_UPDATE_PROFILE", "CMD_IGNORE_UPDATE",
  2977.           "CMD_ABORT", "CMD_FINISHED"
  2978.         ];
  2979.  
  2980.         my $ans;
  2981.         do {
  2982.             $ans = UI_PromptUser($q);
  2983.  
  2984.             if ($ans eq "CMD_VIEW_CHANGES") {
  2985.                 my $oldprofile = serialize_profile($sd{$profile}, $profile);
  2986.                 my $newprofile = $p->{profile};
  2987.                 display_changes($oldprofile, $newprofile);
  2988.             }
  2989.         } until $ans =~ /^CMD_(UPDATE_PROFILE|IGNORE_UPDATE)/;
  2990.  
  2991.         if ($ans eq "CMD_UPDATE_PROFILE") {
  2992.             eval {
  2993.                 my $profile_data =
  2994.                   parse_profile_data($p->{profile}, getprofilefilename($profile), 0);
  2995.                 if ($profile_data) {
  2996.                     attach_profile_data(\%sd, $profile_data);
  2997.                     $changed{$profile} = 1;
  2998.                 }
  2999.  
  3000.                 set_repo_info($sd{$profile}{$profile}, $url, $user, $p->{id});
  3001.  
  3002.                 UI_Info(
  3003.                     sprintf(
  3004.                         gettext("Updated profile %s to revision %s."),
  3005.                         $profile, $p->{id}
  3006.                     )
  3007.                 );
  3008.             };
  3009.  
  3010.             if ($@) {
  3011.                 UI_Info(gettext("Error parsing repository profile."));
  3012.             } else {
  3013.                 $updated = 1;
  3014.             }
  3015.         }
  3016.     }
  3017.     return $updated;
  3018. }
  3019.  
  3020. sub UI_repo_signup {
  3021.  
  3022.     my ($url, $res, $save_config, $newuser, $user, $pass, $email, $signup_okay);
  3023.     $url = $cfg->{repository}{url};
  3024.     do {
  3025.         if ($UI_Mode eq "yast") {
  3026.             SendDataToYast(
  3027.                 {
  3028.                     type     => "dialog-repo-sign-in",
  3029.                     repo_url => $url
  3030.                 }
  3031.             );
  3032.             my ($ypath, $yarg) = GetDataFromYast();
  3033.             $email       = $yarg->{email};
  3034.             $user        = $yarg->{user};
  3035.             $pass        = $yarg->{pass};
  3036.             $newuser     = $yarg->{newuser};
  3037.             $save_config = $yarg->{save_config};
  3038.             if ($yarg->{cancelled} && $yarg->{cancelled} eq "y") {
  3039.                 return;
  3040.             }
  3041.             $DEBUGGING && debug("AppArmor Repository: \n\t " .
  3042.                                 ($newuser eq "1") ?
  3043.                                 "New User\n\temail: [" . $email . "]" :
  3044.                                 "Signin" . "\n\t user[" . $user . "]" .
  3045.                                 "password [" . $pass . "]\n");
  3046.         } else {
  3047.             $newuser = UI_YesNo(gettext("Create New User?"), "n");
  3048.             $user    = UI_GetString(gettext("Username: "), $user);
  3049.             $pass    = UI_GetString(gettext("Password: "), $pass);
  3050.             $email   = UI_GetString(gettext("Email Addr: "), $email)
  3051.                          if ($newuser eq "y");
  3052.             $save_config = UI_YesNo(gettext("Save Configuration? "), "y");
  3053.         }
  3054.  
  3055.         if ($newuser eq "y") {
  3056.             my ($status_ok,$res) = user_register($url, $user, $pass, $email);
  3057.             if ($status_ok) {
  3058.                 $signup_okay = 1;
  3059.             } else {
  3060.                 my $errmsg =
  3061.                    gettext("The Profile Repository server returned the following error:") .
  3062.                    "\n" .  $res?$res:gettext("UNKOWN ERROR") .  "\n" .
  3063.                    gettext("Please re-enter registration information or contact the administrator.");
  3064.                 UI_Important(gettext("Login Error\n") . $errmsg);
  3065.             }
  3066.         } else {
  3067.             my ($status_ok,$res) = user_login($url, $user, $pass);
  3068.             if ($status_ok) {
  3069.                 $signup_okay = 1;
  3070.             } else {
  3071.                 my $errmsg = gettext("Login failure\n Please check username and password and try again.") . "\n" . $res;
  3072.                 UI_Important($errmsg);
  3073.             }
  3074.         }
  3075.     } until $signup_okay;
  3076.  
  3077.     $repo_cfg->{repository}{user} = $user;
  3078.     $repo_cfg->{repository}{pass} = $pass;
  3079.     $repo_cfg->{repository}{email} = $email;
  3080.  
  3081.     write_config("repository.conf", $repo_cfg) if ( $save_config eq "y" );
  3082.  
  3083.     return ($user, $pass);
  3084. }
  3085.  
  3086. sub UI_ask_to_enable_repo {
  3087.  
  3088.     my $q = { };
  3089.     return if ( not defined $cfg->{repository}{url} );
  3090.     $q->{headers} = [
  3091.       gettext("Repository"), $cfg->{repository}{url},
  3092.     ];
  3093.     $q->{explanation} = gettext( "Would you like to enable access to the
  3094. profile repository?" ); $q->{functions} = [ "CMD_ENABLE_REPO",
  3095. "CMD_DISABLE_REPO", "CMD_ASK_LATER", ];
  3096.  
  3097.     my $cmd;
  3098.     do {
  3099.         $cmd = UI_PromptUser($q);
  3100.     } until $cmd =~ /^CMD_(ENABLE_REPO|DISABLE_REPO|ASK_LATER)/;
  3101.  
  3102.     if ($cmd eq "CMD_ENABLE_REPO") {
  3103.         $repo_cfg->{repository}{enabled} = "yes";
  3104.     } elsif ($cmd eq "CMD_DISABLE_REPO") {
  3105.         $repo_cfg->{repository}{enabled} = "no";
  3106.     } elsif ($cmd eq "CMD_ASK_LATER") {
  3107.         $repo_cfg->{repository}{enabled} = "later";
  3108.     }
  3109.  
  3110.     eval { write_config("repository.conf", $repo_cfg) };
  3111.     if ($@) {
  3112.         fatal_error($@);
  3113.     }
  3114. }
  3115.  
  3116.  
  3117. sub UI_ask_to_upload_profiles {
  3118.  
  3119.     my $q = { };
  3120.     $q->{headers} = [
  3121.       gettext("Repository"), $cfg->{repository}{url},
  3122.     ];
  3123.     $q->{explanation} =
  3124.       gettext( "Would you like to upload newly created and changed profiles to
  3125.       the profile repository?" );
  3126.     $q->{functions} = [
  3127.       "CMD_YES", "CMD_NO", "CMD_ASK_LATER",
  3128.     ];
  3129.  
  3130.     my $cmd;
  3131.     do {
  3132.         $cmd = UI_PromptUser($q);
  3133.     } until $cmd =~ /^CMD_(YES|NO|ASK_LATER)/;
  3134.  
  3135.     if ($cmd eq "CMD_NO") {
  3136.         $repo_cfg->{repository}{upload} = "no";
  3137.     } elsif ($cmd eq "CMD_YES") {
  3138.         $repo_cfg->{repository}{upload} = "yes";
  3139.     } elsif ($cmd eq "CMD_ASK_LATER") {
  3140.         $repo_cfg->{repository}{upload} = "later";
  3141.     }
  3142.  
  3143.     eval { write_config("repository.conf", $repo_cfg) };
  3144.     if ($@) {
  3145.         fatal_error($@);
  3146.     }
  3147. }
  3148.  
  3149.  
  3150. sub parse_repo_profile {
  3151.     my ($fqdbin, $repo_url, $profile) = @_;
  3152.  
  3153.     my $profile_data = eval {
  3154.         parse_profile_data($profile->{profile}, getprofilefilename($fqdbin), 0);
  3155.     };
  3156.     if ($@) {
  3157.         print STDERR "PARSING ERROR: $@\n";
  3158.         $profile_data = undef;
  3159.     }
  3160.  
  3161.     if ($profile_data) {
  3162.         set_repo_info($profile_data->{$fqdbin}{$fqdbin}, $repo_url,
  3163.                       $profile->{username}, $profile->{id});
  3164.     }
  3165.  
  3166.     return $profile_data;
  3167. }
  3168.  
  3169.  
  3170. sub set_repo_info {
  3171.     my ($profile_data, $repo_url, $username, $id) = @_;
  3172.  
  3173.     # save repository metadata
  3174.     $profile_data->{repo}{url}  = $repo_url;
  3175.     $profile_data->{repo}{user} = $username;
  3176.     $profile_data->{repo}{id}   = $id;
  3177. }
  3178.  
  3179.  
  3180. sub is_repo_profile {
  3181.     my $profile_data = shift;
  3182.  
  3183.     return $profile_data->{repo}{url}  &&
  3184.            $profile_data->{repo}{user} &&
  3185.            $profile_data->{repo}{id};
  3186. }
  3187.  
  3188.  
  3189. sub get_repo_user_pass {
  3190.     my ($user, $pass);
  3191.  
  3192.     if ($repo_cfg) {
  3193.         $user = $repo_cfg->{repository}{user};
  3194.         $pass = $repo_cfg->{repository}{pass};
  3195.     }
  3196.  
  3197.     unless ($user && $pass) {
  3198.         ($user, $pass) = UI_repo_signup();
  3199.     }
  3200.  
  3201.     return ($user, $pass);
  3202. }
  3203.  
  3204.  
  3205. sub get_preferred_user ($) {
  3206.     my $repo_url = shift;
  3207.     return $cfg->{repository}{preferred_user} || "NOVELL";
  3208. }
  3209.  
  3210.  
  3211. sub repo_is_enabled () {
  3212.     my $enabled;
  3213.     if ($cfg->{repository}{url} &&
  3214.         $repo_cfg &&
  3215.         $repo_cfg->{repository}{enabled} &&
  3216.         $repo_cfg->{repository}{enabled} eq "yes") {
  3217.         $enabled = 1;
  3218.     }
  3219.     return $enabled;
  3220. }
  3221.  
  3222.  
  3223. sub update_repo_profile {
  3224.     my $profile = shift;
  3225.  
  3226.     return undef if ( not is_repo_profile($profile) );
  3227.     my $distro = $cfg->{repository}{distro};
  3228.     my $url    = $profile->{repo}{url};
  3229.     my $user   = $profile->{repo}{user};
  3230.     my $id     = $profile->{repo}{id};
  3231.  
  3232.     UI_BusyStart( gettext("Connecting to repository.....") );
  3233.     my ($status_ok,$res) = fetch_newer_profile( $url,
  3234.                                                 $distro,
  3235.                                                 $user,
  3236.                                                 $id,
  3237.                                                 $profile->{name}
  3238.                                               );
  3239.     UI_BusyStop();
  3240.     if ( ! $status_ok ) {
  3241.         my $errmsg =
  3242.           sprintf(
  3243.             gettext("WARNING: Profile update check failed\nError Detail:\n%s"),
  3244.             defined $res?$res:gettext("UNKNOWN ERROR"));
  3245.         UI_Important($errmsg);
  3246.         $res = undef;
  3247.     }
  3248.     return( $res );
  3249. }
  3250.  
  3251. sub UI_ask_mode_toggles ($$$) {
  3252.     my ($audit_toggle, $owner_toggle, $oldmode) = @_;
  3253.     my $q = { };
  3254.     $q->{headers} = [ ];
  3255. #      "Repository", $cfg->{repository}{url},
  3256. #    ];
  3257.     $q->{explanation} = gettext( "Change mode modifiers");
  3258.  
  3259.     if ($audit_toggle) {
  3260.     $q->{functions} = [ "CMD_AUDIT_OFF" ];
  3261.     } else {
  3262.     $q->{functions} = [ "CMD_AUDIT_NEW" ];
  3263.     push @{$q->{functions}}, "CMD_AUDIT_FULL" if ($oldmode);
  3264.     }
  3265.  
  3266.     if ($owner_toggle) {
  3267.     push @{$q->{functions}}, "CMD_USER_OFF";
  3268.     } else {
  3269.     push @{$q->{functions}}, "CMD_USER_ON";
  3270.     }
  3271.     push @{$q->{functions}}, "CMD_CONTINUE";
  3272.  
  3273.     my $cmd;
  3274.     do {
  3275.         $cmd = UI_PromptUser($q);
  3276.     } until $cmd =~ /^CMD_(AUDIT_OFF|AUDIT_NEW|AUDIT_FULL|USER_ON|USER_OFF|CONTINUE)/;
  3277.  
  3278.     if ($cmd eq "CMD_AUDIT_OFF") {
  3279.     $audit_toggle = 0;
  3280.     } elsif ($cmd eq "CMD_AUDIT_NEW") {
  3281.     $audit_toggle = 1;
  3282.     } elsif ($cmd eq "CMD_AUDIT_FULL") {
  3283.     $audit_toggle = 2;
  3284.     } elsif ($cmd eq "CMD_USER_ON") {
  3285.     $owner_toggle = 1;
  3286.     } elsif ($cmd eq "CMD_USER_OFF") {
  3287.     $owner_toggle = 0;
  3288. #    $owner_toggle++;
  3289. #    $owner_toggle++ if (!$oldmode && $owner_toggle == 2);
  3290. #    $owner_toggle = 0 if ($owner_toggle > 3);
  3291.     }
  3292.     return ($audit_toggle, $owner_toggle);
  3293. }
  3294.  
  3295. sub ask_the_questions {
  3296.     my $found; # do the magic foo-foo
  3297.     for my $sdmode (sort keys %log) {
  3298.  
  3299.         # let them know what sort of changes we're about to list...
  3300.         if ($sdmode eq "PERMITTING") {
  3301.             UI_Info(gettext("Complain-mode changes:"));
  3302.         } elsif ($sdmode eq "REJECTING") {
  3303.             UI_Info(gettext("Enforce-mode changes:"));
  3304.         } else {
  3305.  
  3306.             # if we're not permitting and not rejecting, something's broken.
  3307.             # most likely  the code we're using to build the hash tree of log
  3308.             # entries - this should never ever happen
  3309.             fatal_error(sprintf(gettext('Invalid mode found: %s'), $sdmode));
  3310.         }
  3311.  
  3312.         for my $profile (sort keys %{ $log{$sdmode} }) {
  3313.             my $p = update_repo_profile($sd{$profile}{$profile});
  3314.             UI_SelectUpdatedRepoProfile($profile, $p) if ( $p );
  3315.  
  3316.             $found++;
  3317.  
  3318.             # this sorts the list of hats, but makes sure that the containing
  3319.             # profile shows up in the list first to keep the question order
  3320.             # rational
  3321.             my @hats =
  3322.               grep { $_ ne $profile } keys %{ $log{$sdmode}{$profile} };
  3323.             unshift @hats, $profile
  3324.               if defined $log{$sdmode}{$profile}{$profile};
  3325.  
  3326.             for my $hat (@hats) {
  3327.  
  3328.                 # step through all the capabilities first...
  3329.                 for my $capability (sort keys %{ $log{$sdmode}{$profile}{$hat}{capability} }) {
  3330.  
  3331.                     # we don't care about it if we've already added it to the
  3332.                     # profile
  3333.                     next if profile_known_capability($sd{$profile}{$hat},
  3334.                              $capability);
  3335.  
  3336.                     my $severity = $sevdb->rank(uc("cap_$capability"));
  3337.  
  3338.                     my $defaultoption = 1;
  3339.                     my @options       = ();
  3340.                     my @newincludes;
  3341.                     @newincludes = matchcapincludes($sd{$profile}{$hat},
  3342.                                                     $capability);
  3343.  
  3344.  
  3345.                     my $q = {};
  3346.  
  3347.                     if (@newincludes) {
  3348.                         push @options,
  3349.                           map { "#include <$_>" } sort(uniq(@newincludes));
  3350.                     }
  3351.  
  3352.                     if ( @options ) {
  3353.                         push @options, "capability $capability";
  3354.                         $q->{options}  = [@options];
  3355.                         $q->{selected} = $defaultoption - 1;
  3356.                     }
  3357.  
  3358.                     $q->{headers} = [];
  3359.                     push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3360.                     push @{ $q->{headers} }, gettext("Capability"), $capability;
  3361.                     push @{ $q->{headers} }, gettext("Severity"),   $severity;
  3362.  
  3363.             my $audit_toggle = 0;
  3364.             $q->{functions} = [
  3365.             "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED"
  3366.             ];
  3367.  
  3368.                     # complain-mode events default to allow - enforce defaults
  3369.                     # to deny
  3370.                     $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" : "CMD_DENY";
  3371.  
  3372.                     $seenevents++;
  3373.                     my $done = 0;
  3374.                     while ( not $done ) {
  3375.                         # what did the grand exalted master tell us to do?
  3376.                         my ($ans, $selected) = UI_PromptUser($q);
  3377.  
  3378.             if ($ans =~ /^CMD_AUDIT/) {
  3379.                 $audit_toggle = !$audit_toggle;
  3380.                 my $audit = "";
  3381.                 if ($audit_toggle) {
  3382.                 $q->{functions} = [
  3383.                     "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_OFF", "CMD_ABORT", "CMD_FINISHED"
  3384.                     ];
  3385.                 $audit = "audit ";
  3386.                 } else {
  3387.                 $q->{functions} = [
  3388.                     "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED"
  3389.                     ];
  3390.                 }
  3391.                 $q->{headers} = [];
  3392.                 push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3393.                 push @{ $q->{headers} }, gettext("Capability"), $audit . $capability;
  3394.                 push @{ $q->{headers} }, gettext("Severity"),   $severity;
  3395.  
  3396.                         } if ($ans eq "CMD_ALLOW") {
  3397.  
  3398.                             # they picked (a)llow, so...
  3399.  
  3400.                             my $selection = $options[$selected];
  3401.                             $done = 1;
  3402.                             if ($selection &&
  3403.                                 $selection =~ m/^#include <(.+)>$/) {
  3404.                                 my $deleted = 0;
  3405.                                 my $inc = $1;
  3406.                                 $deleted = delete_duplicates($sd{$profile}{$hat},
  3407.                                                                $inc
  3408.                                                              );
  3409.                                 $sd{$profile}{$hat}{include}{$inc} = 1;
  3410.  
  3411.                                 $changed{$profile} = 1;
  3412.                                 UI_Info(sprintf(
  3413.                                   gettext('Adding #include <%s> to profile.'),
  3414.                                           $inc));
  3415.                                 UI_Info(sprintf(
  3416.                                   gettext('Deleted %s previous matching profile entries.'),
  3417.                                            $deleted)) if $deleted;
  3418.                             }
  3419.                             # stick the capability into the profile
  3420.                             $sd{$profile}{$hat}{allow}{capability}{$capability}{set} = 1;
  3421.                             $sd{$profile}{$hat}{allow}{capability}{$capability}{audit} = $audit_toggle;
  3422.  
  3423.                             # mark this profile as changed
  3424.                             $changed{$profile} = 1;
  3425.                             $done = 1;
  3426.                             # give a little feedback to the user
  3427.                             UI_Info(sprintf(gettext('Adding capability %s to profile.'), $capability));
  3428.                         } elsif ($ans eq "CMD_DENY") {
  3429.                             $sd{$profile}{$hat}{deny}{capability}{$capability}{set} = 1;
  3430.                             # mark this profile as changed
  3431.                             $changed{$profile} = 1;
  3432.                             UI_Info(sprintf(gettext('Denying capability %s to profile.'), $capability));
  3433.                             $done = 1;
  3434.                         } else {
  3435.                             redo;
  3436.                         }
  3437.                     }
  3438.                 }
  3439.  
  3440.                 # and then step through all of the path entries...
  3441.                 for my $path (sort keys %{ $log{$sdmode}{$profile}{$hat}{path} }) {
  3442.  
  3443.                     my $mode = $log{$sdmode}{$profile}{$hat}{path}{$path};
  3444.  
  3445.             # do original profile lookup once.
  3446.  
  3447.             my $allow_mode = 0;
  3448.             my $allow_audit = 0;
  3449.             my $deny_mode = 0;
  3450.             my $deny_audit = 0;
  3451.  
  3452.             my ($fmode, $famode, $imode, $iamode, @fm, @im, $cm, $am, $cam, @m);
  3453.             ($fmode, $famode, @fm) = rematchfrag($sd{$profile}{$hat}, 'allow', $path);
  3454.             $allow_mode |= $fmode if $fmode;
  3455.             $allow_audit |= $famode if $famode;
  3456.             ($imode, $iamode, @im) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path);
  3457.             $allow_mode |= $imode if $imode;
  3458.             $allow_audit |= $iamode if $iamode;
  3459.  
  3460.             ($cm, $cam, @m) = rematchfrag($sd{$profile}{$hat}, 'deny', $path);
  3461.             $deny_mode |= $cm if $cm;
  3462.             $deny_audit |= $cam if $cam;
  3463.             ($cm, $cam, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'deny', $path);
  3464.             $deny_mode |= $cm if $cm;
  3465.             $deny_audit |= $cam if $cam;
  3466.  
  3467.             if ($deny_mode & $AA_MAY_EXEC) {
  3468.             $deny_mode |= $ALL_AA_EXEC_TYPE;
  3469.             }
  3470.  
  3471.             # mask off the modes that have been denied
  3472.             $mode &= ~$deny_mode;
  3473.             $allow_mode &= ~$deny_mode;
  3474.  
  3475.                     # if we had an access(X_OK) request or some other kind of
  3476.                     # event that generates a "PERMITTING x" syslog entry,
  3477.                     # first check if it was already dealt with by a i/p/x
  3478.                     # question due to a exec().  if not, ask about adding ix
  3479.                     # permission.
  3480.                     if ($mode & $AA_MAY_EXEC) {
  3481.  
  3482.                         # get rid of the access() markers.
  3483.                         $mode &= (~$ALL_AA_EXEC_TYPE);
  3484.  
  3485.                         unless ($allow_mode & $allow_mode & $AA_MAY_EXEC) {
  3486.                             $mode |= str_to_mode("ix");
  3487.                         }
  3488.                     }
  3489.  
  3490.                     # if we had an mmap(PROT_EXEC) request, first check if we
  3491.                     # already have added an ix rule to the profile
  3492.                     if ($mode & $AA_EXEC_MMAP) {
  3493.                         # ix implies m.  don't ask if they want to add an "m"
  3494.                         # rule when we already have a matching ix rule.
  3495.                         if ($allow_mode && contains($allow_mode, "ix")) {
  3496.                             $mode &= (~$AA_EXEC_MMAP);
  3497.                         }
  3498.                     }
  3499.  
  3500.                     next unless $mode;
  3501.  
  3502.  
  3503.                     my @matches;
  3504.  
  3505.                     if ($fmode) {
  3506.                         push @matches, @fm;
  3507.                     }
  3508.                     if ($imode) {
  3509.                         push @matches, @im;
  3510.                     }
  3511.  
  3512.                     unless ($allow_mode && mode_contains($allow_mode, $mode)) {
  3513.  
  3514.                         my $defaultoption = 1;
  3515.                         my @options       = ();
  3516.  
  3517.                         # check the path against the available set of include
  3518.                         # files
  3519.                         my @newincludes;
  3520.                         my $includevalid;
  3521.                         for my $incname (keys %include) {
  3522.                             $includevalid = 0;
  3523.  
  3524.                             # don't suggest it if we're already including it,
  3525.                             # that's dumb
  3526.                             next if $sd{$profile}{$hat}{$incname};
  3527.  
  3528.                             # only match includes that can be suggested to
  3529.                             # the user
  3530.                 if ($cfg->{settings}{custom_includes}) {
  3531.                             for my $incm (split(/\s+/,
  3532.                                                 $cfg->{settings}{custom_includes})
  3533.                                          ) {
  3534.                                 $includevalid = 1 if $incname =~ /$incm/;
  3535.                             }
  3536.                 }
  3537.                             $includevalid = 1 if $incname =~ /abstractions/;
  3538.                             next if ($includevalid == 0);
  3539.  
  3540.                             ($cm, $am, @m) = match_include_to_path($incname, 'allow', $path);
  3541.                             if ($cm && mode_contains($cm, $mode)) {
  3542.                 #make sure it doesn't deny $mode
  3543.                 my $dm = match_include_to_path($incname, 'deny', $path);
  3544.                 unless (($mode & $dm) || (grep { $_ eq "/**" } @m)) {
  3545.                                     push @newincludes, $incname;
  3546.                                 }
  3547.                             }
  3548.                         }
  3549.  
  3550.  
  3551.                         # did any match?  add them to the option list...
  3552.                         if (@newincludes) {
  3553.                             push @options,
  3554.                               map { "#include <$_>" }
  3555.                               sort(uniq(@newincludes));
  3556.                         }
  3557.  
  3558.                         # include the literal path in the option list...
  3559.                         push @options, $path;
  3560.  
  3561.                         # match the current path against the globbing list in
  3562.                         # logprof.conf
  3563.                         my @globs = globcommon($path);
  3564.                         if (@globs) {
  3565.                             push @matches, @globs;
  3566.                         }
  3567.  
  3568.                         # suggest any matching globs the user manually entered
  3569.                         for my $userglob (@userglobs) {
  3570.                             push @matches, $userglob
  3571.                               if matchliteral($userglob, $path);
  3572.                         }
  3573.  
  3574.                         # we'll take the cheesy way and order the suggested
  3575.                         # globbing list by length, which is usually right,
  3576.                         # but not always always
  3577.                         push @options,
  3578.                           sort { length($b) <=> length($a) }
  3579.                           grep { $_ ne $path }
  3580.                           uniq(@matches);
  3581.                         $defaultoption = $#options + 1;
  3582.  
  3583.                         my $severity = $sevdb->rank($path, mode_to_str($mode));
  3584.  
  3585.             my $audit_toggle = 0;
  3586.             my $owner_toggle = $cfg->{settings}{default_owner_prompt};
  3587.                         my $done = 0;
  3588.                         while (not $done) {
  3589.  
  3590.                             my $q = {};
  3591.                             $q->{headers} = [];
  3592.                             push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3593.                             push @{ $q->{headers} }, gettext("Path"), $path;
  3594.  
  3595.                             # merge in any previous modes from this run
  3596.                             if ($allow_mode) {
  3597.                 my $str;
  3598. #print "mode: " . print_mode($mode) . " allow: " . print_mode($allow_mode) . "\n";
  3599.                                 $mode |= $allow_mode;
  3600.                 my $tail;
  3601.                 my $prompt_mode;
  3602.                 if ($owner_toggle == 0) {
  3603.                     $prompt_mode = flatten_mode($mode);
  3604.                     $tail = "     " . gettext("(owner permissions off)");
  3605.                 } elsif ($owner_toggle == 1) {
  3606.                     $prompt_mode = $mode;
  3607.                     $tail = "";
  3608.                 } elsif ($owner_toggle == 2) {
  3609.                     $prompt_mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode);
  3610.                     $tail = "     " . gettext("(force new perms to owner)");
  3611.                 } else {
  3612.                     $prompt_mode = owner_flatten_mode($mode);
  3613.                     $tail = "     " . gettext("(force all rule perms to owner)");
  3614.                 }
  3615.  
  3616.                 if ($audit_toggle == 1) {
  3617.                     $str = mode_to_str_user($allow_mode);
  3618.                     $str .= ", " if ($allow_mode);
  3619.                     $str .= "audit " . mode_to_str_user($prompt_mode & ~$allow_mode) . $tail;
  3620.                 } elsif ($audit_toggle == 2) {
  3621.                     $str = "audit " . mode_to_str_user($prompt_mode) . $tail;
  3622.                 } else {
  3623.                     $str = mode_to_str_user($prompt_mode) . $tail;
  3624.                 }
  3625.                                 push @{ $q->{headers} }, gettext("Old Mode"), mode_to_str_user($allow_mode);
  3626.                                 push @{ $q->{headers} }, gettext("New Mode"), $str;
  3627.                             } else {
  3628.                 my $str = "";
  3629.                 if ($audit_toggle) {
  3630.                     $str = "audit ";
  3631.                 }
  3632.                 my $tail;
  3633.                 my $prompt_mode;
  3634.                 if ($owner_toggle == 0) {
  3635.                     $prompt_mode = flatten_mode($mode);
  3636.                     $tail = "     " . gettext("(owner permissions off)");
  3637.                 } elsif ($owner_toggle == 1) {
  3638.                     $prompt_mode = $mode;
  3639.                     $tail = "";
  3640.                 } else {
  3641.                     $prompt_mode = owner_flatten_mode($mode);
  3642.                     $tail = "     " . gettext("(force perms to owner)");
  3643.                 }
  3644.                 $str .= mode_to_str_user($prompt_mode) . $tail;
  3645.                                 push @{ $q->{headers} }, gettext("Mode"), $str; 
  3646.                             }
  3647.                             push @{ $q->{headers} }, gettext("Severity"), $severity;
  3648.  
  3649.                             $q->{options}  = [@options];
  3650.                             $q->{selected} = $defaultoption - 1;
  3651.  
  3652.                             $q->{functions} = [
  3653.                               "CMD_ALLOW", "CMD_DENY", "CMD_GLOB", "CMD_GLOBEXT", "CMD_NEW",
  3654.                               "CMD_ABORT", "CMD_FINISHED", "CMD_OTHER"
  3655.                             ];
  3656.  
  3657.                             $q->{default} =
  3658.                               ($sdmode eq "PERMITTING")
  3659.                               ? "CMD_ALLOW"
  3660.                               : "CMD_DENY";
  3661.  
  3662.                             $seenevents++;
  3663.                             # if they just hit return, use the default answer
  3664.                             my ($ans, $selected) = UI_PromptUser($q);
  3665.  
  3666.                 if ($ans eq "CMD_OTHER") {
  3667.  
  3668.                 ($audit_toggle, $owner_toggle) = UI_ask_mode_toggles($audit_toggle, $owner_toggle, $allow_mode);
  3669.                 } elsif ($ans eq "CMD_USER_TOGGLE") {
  3670.                 $owner_toggle++;
  3671.                 $owner_toggle++ if (!$allow_mode && $owner_toggle == 2);
  3672.                 $owner_toggle = 0 if ($owner_toggle > 3);
  3673.                 } elsif ($ans eq "CMD_ALLOW") {
  3674.                                 $path = $options[$selected];
  3675.                                 $done = 1;
  3676.                                 if ($path =~ m/^#include <(.+)>$/) {
  3677.                                     my $inc = $1;
  3678.                                     my $deleted = 0;
  3679.  
  3680.                                     $deleted = delete_duplicates($sd{$profile}{$hat},
  3681.                                                                   $inc );
  3682.  
  3683.                                     # record the new entry
  3684.                                     $sd{$profile}{$hat}{include}{$inc} = 1;
  3685.  
  3686.                                     $changed{$profile} = 1;
  3687.                                     UI_Info(sprintf(gettext('Adding #include <%s> to profile.'), $inc));
  3688.                                     UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted;
  3689.                                 } else {
  3690.                                     if ($sd{$profile}{$hat}{allow}{path}{$path}{mode}) {
  3691.                                         $mode |= $sd{$profile}{$hat}{allow}{path}{$path}{mode};
  3692.                                     }
  3693.  
  3694.                                     my $deleted = 0;
  3695.                                     for my $entry (keys %{ $sd{$profile}{$hat}{allow}{path} }) {
  3696.  
  3697.                                         next if $path eq $entry;
  3698.  
  3699.                                         if (matchregexp($path, $entry)) {
  3700.  
  3701.                                             # regexp matches, add it's mode to
  3702.                                             # the list to check against
  3703.                                             if (mode_contains($mode,
  3704.                                                 $sd{$profile}{$hat}{allow}{path}{$entry}{mode})) {
  3705.                                                 delete $sd{$profile}{$hat}{allow}{path}{$entry};
  3706.                                                 $deleted++;
  3707.                                             }
  3708.                                         }
  3709.                                     }
  3710.  
  3711.                                     # record the new entry
  3712.                     if ($owner_toggle == 0) {
  3713.                     $mode = flatten_mode($mode);
  3714.                     } elsif ($owner_toggle == 1) {
  3715.                     $mode = $mode;
  3716.                     } elsif ($owner_toggle == 2) {
  3717.                     $mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode);
  3718.                     } elsif  ($owner_toggle == 3) {
  3719.                     $mode = owner_flatten_mode($mode);
  3720.                     }
  3721.                                     $sd{$profile}{$hat}{allow}{path}{$path}{mode} |= $mode;
  3722.                     my $tmpmode = ($audit_toggle == 1) ? $mode & ~$allow_mode : 0;
  3723.                     $tmpmode = ($audit_toggle == 2) ? $mode : $tmpmode;
  3724.                                     $sd{$profile}{$hat}{allow}{path}{$path}{audit} |= $tmpmode;
  3725.  
  3726.                                     $changed{$profile} = 1;
  3727.                                     UI_Info(sprintf(gettext('Adding %s %s to profile.'), $path, mode_to_str_user($mode)));
  3728.                                     UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted;
  3729.                                 }
  3730.                             } elsif ($ans eq "CMD_DENY") {
  3731.                 # record the new entry
  3732.                 $sd{$profile}{$hat}{deny}{path}{$path}{mode} |= $mode & ~$allow_mode;
  3733.                 $sd{$profile}{$hat}{deny}{path}{$path}{audit} |= 0;
  3734.  
  3735.                 $changed{$profile} = 1;
  3736.  
  3737.                                 # go on to the next entry without saving this
  3738.                                 # one
  3739.                                 $done = 1;
  3740.                             } elsif ($ans eq "CMD_NEW") {
  3741.                                 my $arg = $options[$selected];
  3742.                                 if ($arg !~ /^#include/) {
  3743.                                     $ans = UI_GetString(gettext("Enter new path: "), $arg);
  3744.                                     if ($ans) {
  3745.                                         unless (matchliteral($ans, $path)) {
  3746.                                             my $ynprompt = gettext("The specified path does not match this log entry:") . "\n\n";
  3747.                                             $ynprompt .= "  " . gettext("Log Entry") . ":    $path\n";
  3748.                                             $ynprompt .= "  " . gettext("Entered Path") . ": $ans\n\n";
  3749.                                             $ynprompt .= gettext("Do you really want to use this path?") . "\n";
  3750.  
  3751.                                             # we default to no if they just hit return...
  3752.                                             my $key = UI_YesNo($ynprompt, "n");
  3753.  
  3754.                                             next if $key eq "n";
  3755.                                         }
  3756.  
  3757.                                         # save this one for later
  3758.                                         push @userglobs, $ans;
  3759.  
  3760.                                         push @options, $ans;
  3761.                                         $defaultoption = $#options + 1;
  3762.                                     }
  3763.                                 }
  3764.                             } elsif ($ans eq "CMD_GLOB") {
  3765.  
  3766.                                 # do globbing if they don't have an include
  3767.                                 # selected
  3768.                                 my $newpath = $options[$selected];
  3769.                                 chomp $newpath ;
  3770.                                 unless ($newpath =~ /^#include/) {
  3771.                                     # is this entry directory specific
  3772.                                     if ( $newpath =~ m/\/$/ ) {
  3773.                                         # do we collapse to /* or /**?
  3774.                                         if ($newpath =~ m/\/\*{1,2}\/$/) {
  3775.                                             $newpath =~
  3776.                                             s/\/[^\/]+\/\*{1,2}\/$/\/\*\*\//;
  3777.                                         } else {
  3778.                                             $newpath =~ s/\/[^\/]+\/$/\/\*\//;
  3779.                                         }
  3780.                                     } else {
  3781.                                         # do we collapse to /* or /**?
  3782.                                         if ($newpath =~ m/\/\*{1,2}$/) {
  3783.                                             $newpath =~ s/\/[^\/]+\/\*{1,2}$/\/\*\*/;
  3784.                                         } else {
  3785.                                             $newpath =~ s/\/[^\/]+$/\/\*/;
  3786.                                         }
  3787.                                     }
  3788.                                     if ($newpath ne $selected) {
  3789.                                         push @options, $newpath;
  3790.                                         $defaultoption = $#options + 1;
  3791.                                     }
  3792.                                 }
  3793.                             } elsif ($ans eq "CMD_GLOBEXT") {
  3794.  
  3795.                                 # do globbing if they don't have an include
  3796.                                 # selected
  3797.                                 my $newpath = $options[$selected];
  3798.                                 unless ($newpath =~ /^#include/) {
  3799.                                     # do we collapse to /*.ext or /**.ext?
  3800.                                     if ($newpath =~ m/\/\*{1,2}\.[^\/]+$/) {
  3801.                                         $newpath =~ s/\/[^\/]+\/\*{1,2}(\.[^\/]+)$/\/\*\*$1/;
  3802.                                     } else {
  3803.                                         $newpath =~ s/\/[^\/]+(\.[^\/]+)$/\/\*$1/;
  3804.                                     }
  3805.                                     if ($newpath ne $selected) {
  3806.                                         push @options, $newpath;
  3807.                                         $defaultoption = $#options + 1;
  3808.                                     }
  3809.                                 }
  3810.                             } elsif ($ans =~ /\d/) {
  3811.                                 $defaultoption = $ans;
  3812.                             }
  3813.                         }
  3814.                     }
  3815.                 }
  3816.  
  3817.                 # and then step through all of the netdomain entries...
  3818.                 for my $family (sort keys %{$log{$sdmode}
  3819.                                                 {$profile}
  3820.                                                 {$hat}
  3821.                                                 {netdomain}}) {
  3822.  
  3823.                     # TODO - severity handling for net toggles
  3824.                     #my $severity = $sevdb->rank();
  3825.                     for my $sock_type (sort keys %{$log{$sdmode}
  3826.                                                        {$profile}
  3827.                                                        {$hat}
  3828.                                                        {netdomain}
  3829.                                                        {$family}}) {
  3830.  
  3831.                         # we don't care about it if we've already added it to the
  3832.                         # profile
  3833.                         next if ( profile_known_network($sd{$profile}{$hat},
  3834.                             $family,
  3835.                             $sock_type));
  3836.                         my $defaultoption = 1;
  3837.                         my @options       = ();
  3838.                         my @newincludes;
  3839.                         @newincludes = matchnetincludes($sd{$profile}{$hat},
  3840.                                                         $family,
  3841.                                                         $sock_type);
  3842.  
  3843.                         my $q = {};
  3844.  
  3845.                         if (@newincludes) {
  3846.                             push @options,
  3847.                               map { "#include <$_>" } sort(uniq(@newincludes));
  3848.                         }
  3849.  
  3850.                         if ( @options ) {
  3851.                             push @options, "network $family $sock_type";
  3852.                             $q->{options}  = [@options];
  3853.                             $q->{selected} = $defaultoption - 1;
  3854.                         }
  3855.  
  3856.                         $q->{headers} = [];
  3857.                         push @{ $q->{headers} },
  3858.                              gettext("Profile"),
  3859.                              combine_name($profile, $hat);
  3860.                         push @{ $q->{headers} },
  3861.                              gettext("Network Family"),
  3862.                              $family;
  3863.                         push @{ $q->{headers} },
  3864.                              gettext("Socket Type"),
  3865.                              $sock_type;
  3866.  
  3867.             my $audit_toggle = 0;
  3868.  
  3869.                         $q->{functions} = [
  3870.                                             "CMD_ALLOW",
  3871.                                             "CMD_DENY",
  3872.                         "CMD_AUDIT_NEW",
  3873.                                             "CMD_ABORT",
  3874.                                             "CMD_FINISHED"
  3875.                                           ];
  3876.  
  3877.                         # complain-mode events default to allow - enforce defaults
  3878.                         # to deny
  3879.                         $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" :
  3880.                                                                     "CMD_DENY";
  3881.  
  3882.                         $seenevents++;
  3883.  
  3884.                         # what did the grand exalted master tell us to do?
  3885.                         my $done = 0;
  3886.                         while ( not $done ) {
  3887.                             my ($ans, $selected) = UI_PromptUser($q);
  3888.                 if ($ans =~ /^CMD_AUDIT/) {
  3889.                 $audit_toggle = !$audit_toggle;
  3890.                 my $audit = $audit_toggle ? "audit " : "";
  3891.                 if ($audit_toggle) {
  3892.                     $q->{functions} = [
  3893.                     "CMD_ALLOW",
  3894.                     "CMD_DENY",
  3895.                     "CMD_AUDIT_OFF",
  3896.                     "CMD_ABORT",
  3897.                     "CMD_FINISHED"
  3898.                     ];
  3899.                 } else {
  3900.                     $q->{functions} = [
  3901.                     "CMD_ALLOW",
  3902.                     "CMD_DENY",
  3903.                     "CMD_AUDIT_NEW",
  3904.                     "CMD_ABORT",
  3905.                     "CMD_FINISHED"
  3906.                     ];
  3907.                 }
  3908.                 $q->{headers} = [];
  3909.                 push @{ $q->{headers} },
  3910.                 gettext("Profile"),
  3911.                 combine_name($profile, $hat);
  3912.                 push @{ $q->{headers} },
  3913.                 gettext("Network Family"),
  3914.                 $audit . $family;
  3915.                 push @{ $q->{headers} },
  3916.                 gettext("Socket Type"),
  3917.                 $sock_type;
  3918.                             } elsif ($ans eq "CMD_ALLOW") {
  3919.                                 my $selection = $options[$selected];
  3920.                                 $done = 1;
  3921.                                 if ($selection &&
  3922.                                     $selection =~ m/^#include <(.+)>$/) {
  3923.                                     my $inc = $1;
  3924.                                     my $deleted = 0;
  3925.                                     $deleted = delete_duplicates($sd{$profile}{$hat},
  3926.                                                                    $inc
  3927.                                                                  );
  3928.                                     # record the new entry
  3929.                                     $sd{$profile}{$hat}{include}{$inc} = 1;
  3930.  
  3931.                                     $changed{$profile} = 1;
  3932.                                     UI_Info(
  3933.                                       sprintf(
  3934.                                         gettext('Adding #include <%s> to profile.'),
  3935.                                                 $inc));
  3936.                                     UI_Info(
  3937.                                       sprintf(
  3938.                                         gettext('Deleted %s previous matching profile entries.'),
  3939.                                                  $deleted)) if $deleted;
  3940.                                 } else {
  3941.  
  3942.                                     # stick the whole rule into the profile
  3943.                                     $sd{$profile}
  3944.                                        {$hat}
  3945.                        {allow}
  3946.                                        {netdomain}
  3947.                        {audit}
  3948.                                        {$family}
  3949.                                        {$sock_type} = $audit_toggle;
  3950.  
  3951.                                     $sd{$profile}
  3952.                                        {$hat}
  3953.                        {allow}
  3954.                                        {netdomain}
  3955.                        {rule}
  3956.                                        {$family}
  3957.                                        {$sock_type} = 1;
  3958.  
  3959.                                     # mark this profile as changed
  3960.                                     $changed{$profile} = 1;
  3961.  
  3962.                                     # give a little feedback to the user
  3963.                                     UI_Info(sprintf(
  3964.                                            gettext('Adding network access %s %s to profile.'),
  3965.                                                     $family,
  3966.                                                     $sock_type
  3967.                                                    )
  3968.                                            );
  3969.                                 }
  3970.                             } elsif ($ans eq "CMD_DENY") {
  3971.                                 $done = 1;
  3972.                 # record the new entry
  3973.                                     $sd{$profile}
  3974.                                        {$hat}
  3975.                        {deny}
  3976.                                        {netdomain}
  3977.                        {rule}
  3978.                                        {$family}
  3979.                                        {$sock_type} = 1;
  3980.  
  3981.                 $changed{$profile} = 1;
  3982.                                 UI_Info(sprintf(
  3983.                                         gettext('Denying network access %s %s to profile.'),
  3984.                                                 $family,
  3985.                                                 $sock_type
  3986.                                                )
  3987.                                        );
  3988.                             } else {
  3989.                                 redo;
  3990.                             }
  3991.                         }
  3992.                     }
  3993.                 }
  3994.             }
  3995.         }
  3996.     }
  3997. }
  3998.  
  3999. sub delete_net_duplicates {
  4000.     my ($netrules, $incnetrules) = @_;
  4001.     my $deleted = 0;
  4002.     if ( $incnetrules && $netrules ) {
  4003.         my $incnetglob = defined $incnetrules->{all};
  4004.  
  4005.         # See which if any profile rules are matched by the include and can be
  4006.         # deleted
  4007.         for my $fam ( keys %$netrules ) {
  4008.             if ( $incnetglob || (ref($incnetrules->{rule}{$fam}) ne "HASH" &&
  4009.                                  $incnetrules->{rule}{$fam} == 1)) { # include allows
  4010.                                                                # all net or
  4011.                                                                # all fam
  4012.                 if ( ref($netrules->{rule}{$fam}) eq "HASH" ) {
  4013.                     $deleted += ( keys %{$netrules->{rule}{$fam}} );
  4014.                 } else {
  4015.                     $deleted++;
  4016.                 }
  4017.                 delete $netrules->{rule}{$fam};
  4018.             } elsif ( ref($netrules->{rule}{$fam}) ne "HASH" &&
  4019.                       $netrules->{rule}{$fam} == 1 ){
  4020.                 next; # profile has all family
  4021.             } else {
  4022.                 for my $socket_type ( keys %{$netrules->{rule}{$fam}} )  {
  4023.                     if ( defined $incnetrules->{$fam}{$socket_type} ) {
  4024.                         delete $netrules->{$fam}{$socket_type};
  4025.                         $deleted++;
  4026.                     }
  4027.                 }
  4028.             }
  4029.         }
  4030.     }
  4031.     return $deleted;
  4032. }
  4033.  
  4034. sub delete_cap_duplicates ($$) {
  4035.     my ($profilecaps, $inccaps) = @_;
  4036.     my $deleted = 0;
  4037.     if ( $profilecaps && $inccaps ) {
  4038.         for my $capname ( keys %$profilecaps ) {
  4039.             if ( defined $inccaps->{$capname}{set} && $inccaps->{$capname}{set} == 1 ) {
  4040.                delete $profilecaps->{$capname};
  4041.                $deleted++;
  4042.             }
  4043.         }
  4044.     }
  4045.     return $deleted;
  4046. }
  4047.  
  4048. sub delete_path_duplicates ($$$) {
  4049.     my ($profile, $incname, $allow) = @_;
  4050.     my $deleted = 0;
  4051.  
  4052.     for my $entry (keys %{ $profile->{$allow}{path} }) {
  4053.         next if $entry eq "#include <$incname>";
  4054.     my ($cm, $am, @m) = match_include_to_path($incname, $allow, $entry);
  4055.         if ($cm
  4056.             && mode_contains($cm, $profile->{$allow}{path}{$entry}{mode})
  4057.         && mode_contains($am, $profile->{$allow}{path}{$entry}{audit}))
  4058.         {
  4059.             delete $profile->{$allow}{path}{$entry};
  4060.             $deleted++;
  4061.         }
  4062.     }
  4063.     return $deleted;
  4064. }
  4065.  
  4066. sub delete_duplicates (\%$) {
  4067.     my ( $profile, $incname ) = @_;
  4068.     my $deleted = 0;
  4069.  
  4070.     # don't cross delete allow rules covered by denied rules as the coverage
  4071.     # may not be complete.  ie. want to deny a subset of allow, allow a subset
  4072.     # of deny with different perms.
  4073.  
  4074.     ## network rules
  4075.     $deleted += delete_net_duplicates($profile->{allow}{netdomain}, $include{$incname}{$incname}{allow}{netdomain});
  4076.     $deleted += delete_net_duplicates($profile->{deny}{netdomain}, $include{$incname}{$incname}{deny}{netdomain});
  4077.  
  4078.     ## capabilities
  4079.     $deleted += delete_cap_duplicates($profile->{allow}{capability},
  4080.                      $include{$incname}{$incname}{allow}{capability});
  4081.     $deleted += delete_cap_duplicates($profile->{deny}{capability},
  4082.                      $include{$incname}{$incname}{deny}{capability});
  4083.  
  4084.     ## paths
  4085.     $deleted += delete_path_duplicates($profile, $incname, 'allow');
  4086.     $deleted += delete_path_duplicates($profile, $incname, 'deny');
  4087.  
  4088.     return $deleted;
  4089. }
  4090.  
  4091. sub matchnetinclude ($$$) {
  4092.     my ($incname, $family, $type) = @_;
  4093.  
  4094.     my @matches;
  4095.  
  4096.     # scan the include fragments for this profile looking for matches
  4097.     my @includelist = ($incname);
  4098.     my @checked;
  4099.     while (my $name = shift @includelist) {
  4100.         push @checked, $name;
  4101.         return 1
  4102.           if netrules_access_check($include{$name}{$name}{allow}{netdomain}, $family, $type);
  4103.         # if this fragment includes others, check them too
  4104.         if (keys %{ $include{$name}{$name}{include} } &&
  4105.             (grep($name, @checked) == 0) ) {
  4106.             push @includelist, keys %{ $include{$name}{$name}{include} };
  4107.         }
  4108.     }
  4109.     return 0;
  4110. }
  4111.  
  4112. sub matchcapincludes (\%$) {
  4113.     my ($profile, $cap) = @_;
  4114.  
  4115.     # check the path against the available set of include
  4116.     # files
  4117.     my @newincludes;
  4118.     my $includevalid;
  4119.     for my $incname (keys %include) {
  4120.     $includevalid = 0;
  4121.  
  4122.     # don't suggest it if we're already including it,
  4123.     # that's dumb
  4124.     next if $profile->{include}{$incname};
  4125.  
  4126.     # only match includes that can be suggested to
  4127.     # the user
  4128.     if ($cfg->{settings}{custom_includes}) {
  4129.         for my $incm (split(/\s+/,
  4130.                 $cfg->{settings}{custom_includes})) {
  4131.         $includevalid = 1 if $incname =~ /$incm/;
  4132.         }
  4133.     }
  4134.     $includevalid = 1 if $incname =~ /abstractions/;
  4135.     next if ($includevalid == 0);
  4136.  
  4137.     push @newincludes, $incname
  4138.         if ( defined $include{$incname}{$incname}{allow}{capability}{$cap}{set} &&
  4139.          $include{$incname}{$incname}{allow}{capability}{$cap}{set} == 1 );
  4140.     }
  4141.     return @newincludes;
  4142. }
  4143.  
  4144. sub matchnetincludes (\%$$) {
  4145.     my ($profile, $family, $type) = @_;
  4146.  
  4147.     # check the path against the available set of include
  4148.     # files
  4149.     my @newincludes;
  4150.     my $includevalid;
  4151.     for my $incname (keys %include) {
  4152.     $includevalid = 0;
  4153.  
  4154.     # don't suggest it if we're already including it,
  4155.     # that's dumb
  4156.     next if $profile->{include}{$incname};
  4157.  
  4158.     # only match includes that can be suggested to
  4159.     # the user
  4160.     if ($cfg->{settings}{custom_includes}) {
  4161.         for my $incm (split(/\s+/, $cfg->{settings}{custom_includes})) {
  4162.         $includevalid = 1 if $incname =~ /$incm/;
  4163.         }
  4164.     }
  4165.     $includevalid = 1 if $incname =~ /abstractions/;
  4166.     next if ($includevalid == 0);
  4167.  
  4168.     push @newincludes, $incname
  4169.         if matchnetinclude($incname, $family, $type);
  4170.     }
  4171.     return @newincludes;
  4172. }
  4173.  
  4174.  
  4175. sub do_logprof_pass {
  4176.     my $logmark = shift || "";
  4177.  
  4178.     # zero out the state variables for this pass...
  4179.     %t              = ( );
  4180.     %transitions    = ( );
  4181.     %seen           = ( );
  4182.     %sd             = ( );
  4183.     %profilechanges = ( );
  4184.     %prelog         = ( );
  4185.     @log            = ( );
  4186.     %log            = ( );
  4187.     %changed        = ( );
  4188.     %skip           = ( );
  4189.     %filelist       = ( );
  4190.  
  4191.     UI_Info(sprintf(gettext('Reading log entries from %s.'), $filename));
  4192.     UI_Info(sprintf(gettext('Updating AppArmor profiles in %s.'), $profiledir));
  4193.  
  4194.     readprofiles();
  4195.     unless ($sevdb) {
  4196.         $sevdb = new Immunix::Severity("$confdir/severity.db", gettext("unknown
  4197. "));
  4198.     }
  4199.  
  4200.     # we need to be able to break all the way out of deep into subroutine calls
  4201.     # if they select "Finish" so we can take them back out to the genprof prompt
  4202.     eval {
  4203.         unless ($repo_cfg || not defined $cfg->{repository}{url}) {
  4204.             $repo_cfg = read_config("repository.conf");
  4205.             unless ($repo_cfg->{repository}{enabled} &&
  4206.                     ($repo_cfg->{repository}{enabled} eq "yes" ||
  4207.                      $repo_cfg->{repository}{enabled} eq "no")) {
  4208.                 UI_ask_to_enable_repo();
  4209.             }
  4210.         }
  4211.  
  4212.         read_log($logmark);
  4213.  
  4214.         for my $root (@log) {
  4215.             handlechildren(undef, undef, $root);
  4216.         }
  4217.  
  4218.         for my $pid (sort { $a <=> $b } keys %profilechanges) {
  4219.             setprocess($pid, $profilechanges{$pid});
  4220.         }
  4221.  
  4222.         collapselog();
  4223.  
  4224.         ask_the_questions();
  4225.  
  4226.         if ($UI_Mode eq "yast") {
  4227.             if (not $running_under_genprof) {
  4228.                 if ($seenevents) {
  4229.                     my $w = { type => "wizard" };
  4230.                     $w->{explanation} = gettext("The profile analyzer has completed processing the log files.\n\nAll updated profiles will be reloaded");
  4231.                     $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ];
  4232.                     SendDataToYast($w);
  4233.                     my $foo = GetDataFromYast();
  4234.                 } else {
  4235.                     my $w = { type => "wizard" };
  4236.                     $w->{explanation} = gettext("No unhandled AppArmor events were found in the system log.");
  4237.                     $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ];
  4238.                     SendDataToYast($w);
  4239.                     my $foo = GetDataFromYast();
  4240.                 }
  4241.             }
  4242.         }
  4243.     };
  4244.  
  4245.     my $finishing = 0;
  4246.     if ($@) {
  4247.         if ($@ =~ /FINISHING/) {
  4248.             $finishing = 1;
  4249.         } else {
  4250.             die $@;
  4251.         }
  4252.     }
  4253.  
  4254.     save_profiles();
  4255.  
  4256.     if (repo_is_enabled()) {
  4257.         if ( (not defined $repo_cfg->{repository}{upload}) ||
  4258.              ($repo_cfg->{repository}{upload} eq "later") ) {
  4259.         UI_ask_to_upload_profiles();
  4260.         }
  4261.         if ($repo_cfg->{repository}{upload} eq "yes") {
  4262.             sync_profiles();
  4263.         }
  4264.         @created = ();
  4265.     }
  4266.  
  4267.     # if they hit "Finish" we need to tell the caller that so we can exit
  4268.     # all the way instead of just going back to the genprof prompt
  4269.     return $finishing ? "FINISHED" : "NORMAL";
  4270. }
  4271.  
  4272. sub save_profiles {
  4273.     # make sure the profile changes we've made are saved to disk...
  4274.     my @changed = sort keys %changed;
  4275.     #
  4276.     # first make sure that profiles in %changed are active (or actual profiles
  4277.     # in %sd) - this is to handle the sloppiness of setting profiles as changed
  4278.     # when they are parsed in the case of legacy hat code that we want to write
  4279.     # out in an updated format
  4280.     foreach  my $profile_name ( keys %changed ) {
  4281.         if ( ! is_active_profile( $profile_name ) ) {
  4282.             delete $changed{ $profile_name };
  4283.         }
  4284.     }
  4285.     @changed = sort keys %changed;
  4286.  
  4287.     if (@changed) {
  4288.         if ($UI_Mode eq "yast") {
  4289.             my (@selected_profiles, $title, $explanation, %profile_changes);
  4290.             foreach my $prof (@changed) {
  4291.                 my $oldprofile = serialize_profile($original_sd{$prof}, $prof);
  4292.                 my $newprofile = serialize_profile($sd{$prof}, $prof);
  4293.  
  4294.                 $profile_changes{$prof} = get_profile_diff($oldprofile,
  4295.                                                            $newprofile);
  4296.             }
  4297.             $explanation = gettext("Select which profile changes you would like to save to the\nlocal profile set");
  4298.             $title       = gettext("Local profile changes");
  4299.             SendDataToYast(
  4300.                 {
  4301.                     type           => "dialog-select-profiles",
  4302.                     title          => $title,
  4303.                     explanation    => $explanation,
  4304.                     default_select => "true",
  4305.                     get_changelog  => "false",
  4306.                     profiles       => \%profile_changes
  4307.                 }
  4308.             );
  4309.             my ($ypath, $yarg) = GetDataFromYast();
  4310.             if ($yarg->{STATUS} eq "cancel") {
  4311.                 return;
  4312.             } else {
  4313.                 my $selected_profiles_ref = $yarg->{PROFILES};
  4314.                 for my $profile (@$selected_profiles_ref) {
  4315.                     writeprofile_ui_feedback($profile);
  4316.                     reload_base($profile);
  4317.                 }
  4318.             }
  4319.         } else {
  4320.             my $q = {};
  4321.             $q->{title}   = "Changed Local Profiles";
  4322.             $q->{headers} = [];
  4323.  
  4324.             $q->{explanation} =
  4325.               gettext( "The following local profiles were changed.  Would you like to save them?");
  4326.  
  4327.             $q->{functions} = [ "CMD_SAVE_CHANGES",
  4328.                                 "CMD_VIEW_CHANGES",
  4329.                                 "CMD_ABORT", ];
  4330.  
  4331.             $q->{default} = "CMD_VIEW_CHANGES";
  4332.  
  4333.             $q->{options}  = [@changed];
  4334.             $q->{selected} = 0;
  4335.  
  4336.             my ($p, $ans, $arg);
  4337.             do {
  4338.                 ($ans, $arg) = UI_PromptUser($q);
  4339.  
  4340.                 if ($ans eq "CMD_VIEW_CHANGES") {
  4341.                     my $which      = $changed[$arg];
  4342.                     my $oldprofile =
  4343.                       serialize_profile($original_sd{$which}, $which);
  4344.                     my $newprofile = serialize_profile($sd{$which}, $which);
  4345.                     display_changes($oldprofile, $newprofile);
  4346.                 }
  4347.  
  4348.             } until $ans =~ /^CMD_SAVE_CHANGES/;
  4349.  
  4350.             for my $profile (sort keys %changed) {
  4351.                 writeprofile_ui_feedback($profile);
  4352.                 reload_base($profile);
  4353.             }
  4354.         }
  4355.     }
  4356. }
  4357.  
  4358.  
  4359. sub get_pager {
  4360.  
  4361.     if ( $ENV{PAGER} and (-x "/usr/bin/$ENV{PAGER}" ||
  4362.                           -x "/usr/sbin/$ENV{PAGER}" )
  4363.        ) {
  4364.         return $ENV{PAGER};
  4365.     } else {
  4366.         return "less"
  4367.     }
  4368. }
  4369.  
  4370.  
  4371. sub display_text {
  4372.     my ($header, $body) = @_;
  4373.     my $pager = get_pager();
  4374.     if (open(PAGER, "| $pager")) {
  4375.         print PAGER "$header\n\n$body";
  4376.         close(PAGER);
  4377.     }
  4378. }
  4379.  
  4380. sub get_profile_diff {
  4381.     my ($oldprofile, $newprofile) = @_;
  4382.     my $oldtmp = new File::Temp(UNLINK => 0);
  4383.     print $oldtmp $oldprofile;
  4384.     close($oldtmp);
  4385.  
  4386.     my $newtmp = new File::Temp(UNLINK => 0);
  4387.     print $newtmp $newprofile;
  4388.     close($newtmp);
  4389.  
  4390.     my $difftmp = new File::Temp(UNLINK => 0);
  4391.     my @diff;
  4392.     system("diff -u $oldtmp $newtmp > $difftmp");
  4393.     while (<$difftmp>) {
  4394.         push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) ||
  4395.                                 ($_ =~ /^\@\@.*\@\@$/));
  4396.     }
  4397.     unlink($difftmp);
  4398.     unlink($oldtmp);
  4399.     unlink($newtmp);
  4400.     return join("", @diff);
  4401. }
  4402.  
  4403. sub display_changes {
  4404.     my ($oldprofile, $newprofile) = @_;
  4405.  
  4406.     my $oldtmp = new File::Temp( UNLINK => 0 );
  4407.     print $oldtmp $oldprofile;
  4408.     close($oldtmp);
  4409.  
  4410.     my $newtmp = new File::Temp( UNLINK => 0 );
  4411.     print $newtmp $newprofile;
  4412.     close($newtmp);
  4413.  
  4414.     my $difftmp = new File::Temp(UNLINK => 0);
  4415.     my @diff;
  4416.     system("diff -u $oldtmp $newtmp > $difftmp");
  4417.     if ($UI_Mode eq "yast") {
  4418.         while (<$difftmp>) {
  4419.             push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) ||
  4420.                                     ($_ =~ /^\@\@.*\@\@$/));
  4421.         }
  4422.         UI_LongMessage(gettext("Profile Changes"), join("", @diff));
  4423.     } else {
  4424.         system("less $difftmp");
  4425.     }
  4426.  
  4427.     unlink($difftmp);
  4428.     unlink($oldtmp);
  4429.     unlink($newtmp);
  4430. }
  4431.  
  4432. sub setprocess ($$) {
  4433.     my ($pid, $profile) = @_;
  4434.  
  4435.     # don't do anything if the process exited already...
  4436.     return unless -e "/proc/$pid/attr/current";
  4437.  
  4438.     return unless open(CURR, "/proc/$pid/attr/current");
  4439.     my $current = <CURR>;
  4440.     return unless $current;
  4441.     chomp $current;
  4442.     close(CURR);
  4443.  
  4444.     # only change null profiles
  4445.     return unless $current =~ /null(-complain)*-profile/;
  4446.  
  4447.     return unless open(STAT, "/proc/$pid/stat");
  4448.     my $stat = <STAT>;
  4449.     chomp $stat;
  4450.     close(STAT);
  4451.  
  4452.     return unless $stat =~ /^\d+ \((\S+)\) /;
  4453.     my $currprog = $1;
  4454.  
  4455.     open(CURR, ">/proc/$pid/attr/current") or return;
  4456.     print CURR "setprofile $profile";
  4457.     close(CURR);
  4458. }
  4459.  
  4460. sub collapselog () {
  4461.     for my $sdmode (keys %prelog) {
  4462.         for my $profile (keys %{ $prelog{$sdmode} }) {
  4463.             for my $hat (keys %{ $prelog{$sdmode}{$profile} }) {
  4464.                 for my $path (keys %{ $prelog{$sdmode}{$profile}{$hat}{path} }) {
  4465.  
  4466.                     my $mode = $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  4467.  
  4468.                     # we want to ignore anything from the log that's already
  4469.                     # in the profile
  4470.                     my $combinedmode = 0;
  4471.  
  4472.                     # is it in the original profile?
  4473.                     if ($sd{$profile}{$hat}{allow}{path}{$path}) {
  4474.                         $combinedmode |= $sd{$profile}{$hat}{allow}{path}{$path}{mode};
  4475.                     }
  4476.  
  4477.                     # does path match any regexps in original profile?
  4478.                     $combinedmode |= rematchfrag($sd{$profile}{$hat}, 'allow', $path);
  4479.  
  4480.                     # does path match anything pulled in by includes in
  4481.                     # original profile?
  4482.                     $combinedmode |= match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path);
  4483.  
  4484.                     # if we found any matching entries, do the modes match?
  4485.                     unless ($combinedmode && mode_contains($combinedmode, $mode)) {
  4486.  
  4487.                         # merge in any previous modes from this run
  4488.                         if ($log{$sdmode}{$profile}{$hat}{$path}) {
  4489.                             $mode |= $log{$sdmode}{$profile}{$hat}{path}{$path};
  4490.                         }
  4491.  
  4492.                         # record the new entry
  4493.                         $log{$sdmode}{$profile}{$hat}{path}{$path} = $mode;
  4494.                     }
  4495.                 }
  4496.  
  4497.                 for my $capability (keys %{ $prelog{$sdmode}{$profile}{$hat}{capability} }) {
  4498.  
  4499.                     # if we don't already have this capability in the profile,
  4500.                     # add it
  4501.                     unless ($sd{$profile}{$hat}{allow}{capability}{$capability}{set}) {
  4502.                         $log{$sdmode}{$profile}{$hat}{capability}{$capability} = 1;
  4503.                     }
  4504.                 }
  4505.  
  4506.                 # Network toggle handling
  4507.                 my $ndref = $prelog{$sdmode}{$profile}{$hat}{netdomain};
  4508.                 for my $family ( keys %{$ndref} ) {
  4509.                     for my $sock_type ( keys %{$ndref->{$family}} ) {
  4510.                         unless ( profile_known_network($sd{$profile}{$hat},
  4511.                                $family, $sock_type)) {
  4512.                             $log{$sdmode}
  4513.                                 {$profile}
  4514.                                 {$hat}
  4515.                                 {netdomain}
  4516.                                 {$family}
  4517.                                 {$sock_type}=1;
  4518.                         }
  4519.                     }
  4520.                 }
  4521.             }
  4522.         }
  4523.     }
  4524. }
  4525.  
  4526. sub profilemode ($) {
  4527.     my $mode = shift;
  4528.  
  4529.     my $modifier = ($mode =~ m/[iupUP]/)[0];
  4530.     if ($modifier) {
  4531.         $mode =~ s/[iupUPx]//g;
  4532.         $mode .= $modifier . "x";
  4533.     }
  4534.  
  4535.     return $mode;
  4536. }
  4537.  
  4538. # kinky.
  4539. sub commonprefix (@) { (join("\0", @_) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0] }
  4540. sub commonsuffix (@) { reverse(((reverse join("\0", @_)) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0]); }
  4541.  
  4542. sub uniq (@) {
  4543.     my %seen;
  4544.     my @result = sort grep { !$seen{$_}++ } @_;
  4545.     return @result;
  4546. }
  4547.  
  4548. our $MODE_MAP_RE = "r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N";
  4549. our $LOG_MODE_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix";
  4550. our $PROFILE_MODE_RE = "r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix";
  4551. our $PROFILE_MODE_NT_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix";
  4552. our $PROFILE_MODE_DENY_RE = "r|w|l|m|k|a|x";
  4553.  
  4554. sub split_log_mode($) {
  4555.     my $mode = shift;
  4556.     my $user = "";
  4557.     my $other = "";
  4558.  
  4559.     if ($mode =~ /(.*?)::(.*)/) {
  4560.     $user = $1 if ($1);
  4561.     $other = $2 if ($2);
  4562.     } else {
  4563.     $user = $mode;
  4564.     $other = $mode;
  4565.     }
  4566.     return ($user, $other);
  4567. }
  4568.  
  4569. sub map_log_mode ($) {
  4570.     my $mode = shift;
  4571.     return $mode;
  4572. #    $mode =~ s/(.*l.*)::.*/$1/ge;
  4573. #    $mode =~ s/.*::(.*l.*)/$1/ge;
  4574. #    $mode =~ s/:://;
  4575. #     return $mode;
  4576. #    return $1;
  4577. }
  4578.  
  4579. sub hide_log_mode($) {
  4580.     my $mode = shift;
  4581.  
  4582.     $mode =~ s/:://;
  4583.     return $mode;
  4584. }
  4585.  
  4586. sub validate_log_mode ($) {
  4587.     my $mode = shift;
  4588.  
  4589.     return ($mode =~ /^($LOG_MODE_RE)+$/) ? 1 : 0;
  4590. }
  4591.  
  4592. sub validate_profile_mode ($$$) {
  4593.     my ($mode, $allow, $nt_name) = @_;
  4594.  
  4595.     if ($allow eq 'deny') {
  4596.     return ($mode =~ /^($PROFILE_MODE_DENY_RE)+$/) ? 1 : 0;
  4597.     } elsif ($nt_name) {
  4598.     return ($mode =~ /^($PROFILE_MODE_NT_RE)+$/) ? 1 : 0;
  4599.     }
  4600.  
  4601.     return ($mode =~ /^($PROFILE_MODE_RE)+$/) ? 1 : 0;
  4602. }
  4603.  
  4604. # modes internally are stored as a bit Mask
  4605. sub sub_str_to_mode($) {
  4606.     my $str = shift;
  4607.     my $mode = 0;
  4608.  
  4609.     return 0 if (not $str);
  4610.  
  4611.     while ($str =~ s/(${MODE_MAP_RE})//) {
  4612.     my $tmp = $1;
  4613. #print "found mode $1\n";
  4614.  
  4615.     if ($tmp && $MODE_HASH{$tmp}) {
  4616.         $mode |= $MODE_HASH{$tmp};
  4617.     } else {
  4618. #print "found mode $tmp\n";
  4619.     }
  4620.     }
  4621.  
  4622. #my $tmp = mode_to_str($mode);
  4623. #print "parsed_mode $mode\n";
  4624.     return $mode;
  4625. }
  4626.  
  4627. sub print_mode ($) {
  4628.     my $mode = shift;
  4629.  
  4630.     my ($user, $other) = split_mode($mode);
  4631.     my $str = sub_mode_to_str($user) . "::" . sub_mode_to_str($other);
  4632.  
  4633.     return $str;
  4634. }
  4635.  
  4636. sub str_to_mode ($) {
  4637.     my $str = shift;
  4638.  
  4639.     return 0 if (not $str);
  4640.  
  4641.     my ($user, $other) = split_log_mode($str);
  4642.  
  4643. #print "str: $str  user: $user, other $other\n";
  4644.     # we only allow user or all
  4645.     $user = $other if (!$user);
  4646.  
  4647.     my $mode = sub_str_to_mode($user);
  4648.     $mode |= (sub_str_to_mode($other) << $AA_OTHER_SHIFT);
  4649.  
  4650. #print "user: $user " .sub_str_to_mode($user) . " other: $other " . (sub_str_to_mode($other) << $AA_OTHER_SHIFT) . " mode = $mode\n";
  4651.  
  4652.     return $mode;
  4653. }
  4654.  
  4655. sub log_str_to_mode($$$) {
  4656.     my ($profile, $str, $nt_name) = @_;
  4657.  
  4658.     my $mode = str_to_mode($str);
  4659.  
  4660.     # this will cover both nx and nix
  4661.     if (contains($mode, "Nx")) {
  4662.     # need to transform to px, cx
  4663.  
  4664.     if ($nt_name =~ /(.+?)\/\/(.+?)/) {
  4665.         my ($lprofile, $lhat) = @_;
  4666.         my $tmode = 0;
  4667.         if ($profile eq $profile) {
  4668.         if ($mode & ($AA_MAY_EXEC)) {
  4669.             $tmode = str_to_mode("Cx::");
  4670.         }
  4671.         if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) {
  4672.             $tmode |= str_to_mode("Cx");
  4673.         }
  4674.         $nt_name = $lhat;
  4675.         } else {
  4676.         if ($mode & ($AA_MAY_EXEC)) {
  4677.             $tmode = str_to_mode("Px::");
  4678.         }
  4679.         if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) {
  4680.             $tmode |= str_to_mode("Px");
  4681.         }
  4682.         $nt_name = $lhat;
  4683.         }
  4684.         $mode = ($mode & ~(str_to_mode("Nx")));
  4685.         $mode |= $tmode;
  4686.     }
  4687.     }
  4688.     return ($mode, $nt_name);
  4689. }
  4690.  
  4691. sub split_mode ($) {
  4692.     my $mode = shift;
  4693.  
  4694.     my $user = $mode & $AA_USER_MASK;
  4695.     my $other = ($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK;
  4696.  
  4697.     return ($user, $other);
  4698. }
  4699.  
  4700. sub is_user_mode ($) {
  4701.     my $mode = shift;
  4702.  
  4703.     my ($user, $other) = split_mode($mode);
  4704.  
  4705.     if ($user && !$other) {
  4706.     return 1;
  4707.     }
  4708.     return 0;
  4709. }
  4710.  
  4711. sub sub_mode_to_str($) {
  4712.     my $mode = shift;
  4713.     my $str = "";
  4714.  
  4715.     # "w" implies "a"
  4716.     $mode &= (~$AA_MAY_APPEND) if ($mode & $AA_MAY_WRITE);
  4717.     $str .= "m" if ($mode & $AA_EXEC_MMAP);
  4718.     $str .= "r" if ($mode & $AA_MAY_READ);
  4719.     $str .= "w" if ($mode & $AA_MAY_WRITE);
  4720.     $str .= "a" if ($mode & $AA_MAY_APPEND);
  4721.     $str .= "l" if ($mode & $AA_MAY_LINK);
  4722.     $str .= "k" if ($mode & $AA_MAY_LOCK);
  4723.     if ($mode & $AA_EXEC_UNCONFINED) {
  4724.     if ($mode & $AA_EXEC_UNSAFE) {
  4725.         $str .= "u";
  4726.     } else {
  4727.         $str .= "U";
  4728.     }
  4729.     }
  4730.     if ($mode & ($AA_EXEC_PROFILE | $AA_EXEC_NT)) {
  4731.     if ($mode & $AA_EXEC_UNSAFE) {
  4732.         $str .= "p";
  4733.     } else {
  4734.         $str .= "P";
  4735.     }
  4736.     }
  4737.     if ($mode & $AA_EXEC_CHILD) {
  4738.     if ($mode & $AA_EXEC_UNSAFE) {
  4739.         $str .= "c";
  4740.     } else {
  4741.         $str .= "C";
  4742.     }
  4743.     }
  4744.     $str .= "i" if ($mode & $AA_EXEC_INHERIT);
  4745.     $str .= "x" if ($mode & $AA_MAY_EXEC);
  4746.  
  4747.     return $str;
  4748. }
  4749.  
  4750. sub flatten_mode ($) {
  4751.     my $mode = shift;
  4752.  
  4753.     return 0 if (!$mode);
  4754.  
  4755.     $mode = ($mode & $AA_USER_MASK) | (($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK);
  4756.     $mode |= ($mode << $AA_OTHER_SHIFT);
  4757. }
  4758.  
  4759. sub mode_to_str ($) {
  4760.     my $mode = shift;
  4761.     $mode = flatten_mode($mode);
  4762.     return sub_mode_to_str($mode);
  4763. }
  4764.  
  4765. sub owner_flatten_mode($) {
  4766.     my $mode = shift;
  4767.     $mode = flatten_mode($mode) & $AA_USER_MASK;
  4768.     return $mode;
  4769. }
  4770.  
  4771. sub mode_to_str_user ($) {
  4772.     my $mode = shift;
  4773.  
  4774.     my ($user, $other) = split_mode($mode);
  4775.  
  4776.     my $str = "";
  4777.     $user = 0 if (!$user);
  4778.     $other = 0 if (!$other);
  4779.  
  4780.     if ($user & ~$other) {
  4781.     # more user perms than other
  4782.     $str = sub_mode_to_str($other). " + " if ($other);
  4783.     $str .= "owner " . sub_mode_to_str($user & ~$other);
  4784.     } elsif (is_user_mode($mode)) {
  4785.     $str = "owner " . sub_mode_to_str($user);
  4786.     } else {
  4787.     $str = sub_mode_to_str(flatten_mode($mode));
  4788.     }
  4789.     return $str;
  4790. }
  4791.  
  4792. sub mode_contains ($$) {
  4793.     my ($mode, $subset) = @_;
  4794.  
  4795.     # "w" implies "a"
  4796.     if ($mode & $AA_MAY_WRITE) {
  4797.     $mode |= $AA_MAY_APPEND;
  4798.     }
  4799.     if ($mode & ($AA_MAY_WRITE << $AA_OTHER_SHIFT)) {
  4800.     $mode |= ($AA_MAY_APPEND << $AA_OTHER_SHIFT);
  4801.     }
  4802.  
  4803.     # "?ix" implies "m"
  4804.     if ($mode & $AA_EXEC_INHERIT) {
  4805.     $mode |= $AA_EXEC_MMAP;
  4806.     }
  4807.     if ($mode & ($AA_EXEC_INHERIT << $AA_OTHER_SHIFT)) {
  4808.     $mode |= ($AA_EXEC_MMAP << $AA_OTHER_SHIFT);
  4809.     }
  4810.  
  4811.     return (($mode & $subset) == $subset);
  4812. }
  4813.  
  4814. sub contains ($$) {
  4815.     my ($mode, $str) = @_;
  4816.  
  4817.     return mode_contains($mode, str_to_mode($str));
  4818. }
  4819.  
  4820. # isSkippableFile - return true if filename matches something that
  4821. # should be skipped (rpm backup files, dotfiles, emacs backup files
  4822. # Annoyingly, this needs to be kept in sync with the skipped files
  4823. # in the apparmor initscript.
  4824. sub isSkippableFile($) {
  4825.     my $path = shift;
  4826.  
  4827.     return ($path =~ /(^|\/)\.[^\/]*$/
  4828.             || $path =~ /\.rpm(save|new)$/
  4829.             || $path =~ /\.dpkg-(old|new)$/
  4830.         || $path =~ /\.swp$/
  4831.             || $path =~ /\~$/);
  4832. }
  4833.  
  4834. # isSkippableDir - return true if directory matches something that
  4835. # should be skipped (cache directory, symlink directories, etc.)
  4836. sub isSkippableDir($) {
  4837.     my $path = shift;
  4838.  
  4839.     return ($path eq "disable"
  4840.             || $path eq "cache"
  4841.             || $path eq "force-complain");
  4842. }
  4843.  
  4844. sub checkIncludeSyntax($) {
  4845.     my $errors = shift;
  4846.  
  4847.     if (opendir(SDDIR, $profiledir)) {
  4848.         my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR);
  4849.         close(SDDIR);
  4850.         while (my $id = shift @incdirs) {
  4851.             next if isSkippableDir($id);
  4852.             if (opendir(SDDIR, "$profiledir/$id")) {
  4853.                 for my $path (grep { !/^\./ } readdir(SDDIR)) {
  4854.                     chomp($path);
  4855.                     next if isSkippableFile($path);
  4856.                     if (-f "$profiledir/$id/$path") {
  4857.                         my $file = "$id/$path";
  4858.                         $file =~ s/$profiledir\///;
  4859.                         eval { loadinclude($file); };
  4860.                         if ( defined $@ && $@ ne "" ) {
  4861.                             push @$errors, $@;
  4862.                         }
  4863.                     } elsif (-d "$id/$path") {
  4864.                         push @incdirs, "$id/$path";
  4865.                     }
  4866.                 }
  4867.                 closedir(SDDIR);
  4868.             }
  4869.         }
  4870.     }
  4871.     return $errors;
  4872. }
  4873.  
  4874. sub checkProfileSyntax ($) {
  4875.     my $errors = shift;
  4876.  
  4877.     # Check the syntax of profiles
  4878.  
  4879.     opendir(SDDIR, $profiledir)
  4880.       or fatal_error "Can't read AppArmor profiles in $profiledir.";
  4881.     for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) {
  4882.         next if isSkippableFile($file);
  4883.         my $err = readprofile("$profiledir/$file", \&printMessageErrorHandler, 1);
  4884.         if (defined $err and $err ne "") {
  4885.             push @$errors, $err;
  4886.         }
  4887.     }
  4888.     closedir(SDDIR);
  4889.     return $errors;
  4890. }
  4891.  
  4892. sub printMessageErrorHandler ($) {
  4893.     my $message = shift;
  4894.     return $message;
  4895. }
  4896.  
  4897. sub readprofiles () {
  4898.     opendir(SDDIR, $profiledir)
  4899.       or fatal_error "Can't read AppArmor profiles in $profiledir.";
  4900.     for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) {
  4901.         next if isSkippableFile($file);
  4902.         readprofile("$profiledir/$file", \&fatal_error, 1);
  4903.     }
  4904.     closedir(SDDIR);
  4905. }
  4906.  
  4907. sub readinactiveprofiles () {
  4908.     return if ( ! -e $extraprofiledir );
  4909.     opendir(ESDDIR, $extraprofiledir) or
  4910.       fatal_error "Can't read AppArmor profiles in $extraprofiledir.";
  4911.     for my $file (grep { -f "$extraprofiledir/$_" } readdir(ESDDIR)) {
  4912.         next if $file =~ /\.rpm(save|new)|README$/;
  4913.         readprofile("$extraprofiledir/$file", \&fatal_error, 0);
  4914.     }
  4915.     closedir(ESDDIR);
  4916. }
  4917.  
  4918. sub readprofile ($$$) {
  4919.     my $file          = shift;
  4920.     my $error_handler = shift;
  4921.     my $active_profile = shift;
  4922.     if (open(SDPROF, "$file")) {
  4923.         local $/;
  4924.         my $data = <SDPROF>;
  4925.         close(SDPROF);
  4926.  
  4927.         eval {
  4928.             my $profile_data = parse_profile_data($data, $file, 0);
  4929.             if ($profile_data && $active_profile) {
  4930.                 attach_profile_data(\%sd, $profile_data);
  4931.                 attach_profile_data(\%original_sd, $profile_data);
  4932.             } elsif ( $profile_data ) {
  4933.                 attach_profile_data(\%extras,      $profile_data);
  4934.             }
  4935.         };
  4936.  
  4937.         # if there were errors loading the profile, call the error handler
  4938.         if ($@) {
  4939.             $@ =~ s/\n$//;
  4940.             return &$error_handler($@);
  4941.         }
  4942.     } else {
  4943.         $DEBUGGING && debug "readprofile: can't read $file - skipping";
  4944.     }
  4945. }
  4946.  
  4947. sub attach_profile_data {
  4948.     my ($profiles, $profile_data) = @_;
  4949.  
  4950.     # make deep copies of the profile data so that if we change one set of
  4951.     # profile data, we're not changing others because of sharing references
  4952.     for my $p ( keys %$profile_data) {
  4953.           $profiles->{$p} = dclone($profile_data->{$p});
  4954.     }
  4955. }
  4956.  
  4957. sub parse_profile_data {
  4958.     my ($data, $file, $do_include) = @_;
  4959.  
  4960.  
  4961.     my ($profile_data, $profile, $hat, $in_contained_hat, $repo_data,
  4962.         @parsed_profiles);
  4963.     my $initial_comment = "";
  4964.  
  4965.     if ($do_include) {
  4966.     $profile = $file;
  4967.     $hat = $file;
  4968.     }
  4969.  
  4970.     for (split(/\n/, $data)) {
  4971.         chomp;
  4972.  
  4973.         # we don't care about blank lines
  4974.         next if /^\s*$/;
  4975.  
  4976.         # start of a profile...
  4977.         if (m/^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/) {
  4978.             # if we run into the start of a profile while we're already in a
  4979.             # profile, something's wrong...
  4980.             if ($profile) {
  4981.         unless (($profile eq $hat) and $4) {
  4982.             die "$profile profile in $file contains syntax errors.\n";
  4983.         }
  4984.         }
  4985.  
  4986.             # we hit the start of a profile, keep track of it...
  4987.         if ($profile && ($profile eq $hat) && $4) {
  4988.         # local profile
  4989.         $hat = $4;
  4990.         $in_contained_hat = 1;
  4991.         $profile_data->{$profile}{$hat}{profile} = 1;
  4992.         } else {
  4993.         $profile  = $2 || $4;
  4994.         # hat is same as profile name if we're not in a hat
  4995.         ($profile, $hat) = split /\/\//, $profile;
  4996.         $in_contained_hat = 0;
  4997.         if ($hat) {
  4998.             $profile_data->{$profile}{$hat}{external} = 1;
  4999.         }
  5000.  
  5001.         $hat ||= $profile;
  5002.         }
  5003.  
  5004.             my $flags = $7;
  5005.  
  5006.             # deal with whitespace in profile and hat names.
  5007.             $profile = strip_quotes($profile);
  5008.             $hat     = strip_quotes($hat) if $hat;
  5009.  
  5010.         # save off the name and filename
  5011.         $profile_data->{$profile}{$hat}{name} = $profile;
  5012.         $profile_data->{$profile}{$hat}{filename} = $file;
  5013.         $filelist{$file}{profiles}{$profile}{$hat} = 1;
  5014.  
  5015.             # keep track of profile flags
  5016.         $profile_data->{$profile}{$hat}{flags} = $flags;
  5017.  
  5018.             $profile_data->{$profile}{$hat}{allow}{netdomain} = { };
  5019.             $profile_data->{$profile}{$hat}{allow}{path} = { };
  5020.  
  5021.             # store off initial comment if they have one
  5022.             $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment
  5023.               if $initial_comment;
  5024.             $initial_comment = "";
  5025.  
  5026.             if ($repo_data) {
  5027.                 $profile_data->{$profile}{$profile}{repo}{url}  = $repo_data->{url};
  5028.                 $profile_data->{$profile}{$profile}{repo}{user} = $repo_data->{user};
  5029.                 $profile_data->{$profile}{$profile}{repo}{id}   = $repo_data->{id};
  5030.                 $repo_data = undef;
  5031.             }
  5032.  
  5033.         } elsif (m/^\s*\}\s*(#.*)?$/) { # end of a profile...
  5034.  
  5035.             # if we hit the end of a profile when we're not in one, something's
  5036.             # wrong...
  5037.             if (not $profile) {
  5038.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5039.             }
  5040.  
  5041.             if ($in_contained_hat) {
  5042.                 $hat = $profile;
  5043.                 $in_contained_hat = 0;
  5044.             } else {
  5045.                 push @parsed_profiles, $profile;
  5046.                 # mark that we're outside of a profile now...
  5047.                 $profile = undef;
  5048.             }
  5049.  
  5050.             $initial_comment = "";
  5051.  
  5052.         } elsif (m/^\s*(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$/) {  # capability entry
  5053.             if (not $profile) {
  5054.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5055.             }
  5056.  
  5057.         my $audit = $1 ? 1 : 0;
  5058.         my $allow = $2 ? 'deny' : 'allow';
  5059.         $allow = 'deny' if ($2);
  5060.             my $capability = $3;
  5061.             $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{set} = 1;
  5062.             $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{audit} = $audit;
  5063.         } elsif (m/^\s*set capability\s+(\S+)\s*,\s*(#.*)?$/) {  # capability entry
  5064.             if (not $profile) {
  5065.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5066.             }
  5067.  
  5068.             my $capability = $1;
  5069.             $profile_data->{$profile}{$hat}{set_capability}{$capability} = 1;
  5070.  
  5071.     } elsif (m/^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$/) { # for now just keep link
  5072.             if (not $profile) {
  5073.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5074.             }
  5075.         my $audit = $1 ? 1 : 0;
  5076.         my $allow = $2 ? 'deny' : 'allow';
  5077.  
  5078.         my $subset = $4;
  5079.             my $link = strip_quotes($7);
  5080.         my $value = strip_quotes($8);
  5081.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{to} = $value;
  5082.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} |= $AA_MAY_LINK;
  5083.         if ($subset) {
  5084.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} |= $AA_LINK_SUBSET;
  5085.         }
  5086.         if ($audit) {
  5087.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} |= $AA_LINK_SUBSET;
  5088.         } else {
  5089.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} |= 0;
  5090.         }
  5091.  
  5092.     } elsif (m/^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$/) { # for now just keep change_profile
  5093.             if (not $profile) {
  5094.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5095.             }
  5096.             my $cp = strip_quotes($1);
  5097.  
  5098.             $profile_data->{$profile}{$hat}{change_profile}{$cp} = 1;
  5099.     } elsif (m/^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$/) { # never do anything with aliases just keep them
  5100.             my $from = strip_quotes($1);
  5101.         my $to = strip_quotes($2);
  5102.  
  5103.             if ($profile) {
  5104.         $profile_data->{$profile}{$hat}{alias}{$from} = $to;
  5105.         } else {
  5106.         unless (exists $filelist{$file}) {
  5107.             $filelist{$file} = { };
  5108.         }
  5109.         $filelist{$file}{alias}{$from} = $to;
  5110.         }
  5111.  
  5112.        } elsif (m/^\s*set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$/) { # never do anything with rlimits just keep them
  5113.        if (not $profile) {
  5114.            die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5115.        }
  5116.        my $from = $1;
  5117.            my $to = $2;
  5118.  
  5119.        $profile_data->{$profile}{$hat}{rlimit}{$from} = $to;
  5120.  
  5121.         } elsif (/^\s*(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$/i) { # boolean definition
  5122.        if (not $profile) {
  5123.            die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5124.        }
  5125.        my $bool_var = $1;
  5126.            my $value = $2;
  5127.  
  5128.        $profile_data->{$profile}{$hat}{lvar}{$bool_var} = $value;
  5129.         } elsif (/^\s*(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\+?=\s*(.+?)\s*,?\s*(#.*)?$/) { # variable additions both += and = doesn't mater
  5130.        my $list_var = strip_quotes($1);
  5131.            my $value = strip_quotes($2);
  5132.  
  5133.        if ($profile) {
  5134.            unless (exists $profile_data->{$profile}{$hat}{lvar}) {
  5135.            # create lval hash by sticking an empty list into list_var
  5136.            my @empty = ();
  5137.            $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@empty;
  5138.            }
  5139.  
  5140.            store_list_var($profile_data->{$profile}{$hat}{lvar}, $list_var, $value);
  5141.        } else  {
  5142.            unless (exists $filelist{$file}{lvar}) {
  5143.            # create lval hash by sticking an empty list into list_var
  5144.            my @empty = ();
  5145.            $filelist{$file}{lvar}{$list_var} = \@empty;
  5146.            }
  5147.  
  5148.            store_list_var($filelist{$file}{lvar}, $list_var, $value);
  5149.        }
  5150.         } elsif (m/^\s*if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean
  5151.         } elsif (m/^\s*if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- variable defined
  5152.         } elsif (m/^\s*if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean defined
  5153.         } elsif (m/^\s*(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$/) {     # path entry
  5154.             if (not $profile) {
  5155.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5156.             }
  5157.  
  5158.         my $audit = $1 ? 1 : 0;
  5159.         my $allow = $2 ? 'deny' : 'allow';
  5160.         my $user = $3 ? 1 : 0;
  5161.             my ($path, $mode, $nt_name) = ($4, $5, $7);
  5162.  
  5163.             # strip off any trailing spaces.
  5164.             $path =~ s/\s+$//;
  5165.             $nt_name =~ s/\s+$// if $nt_name;
  5166.  
  5167.             $path = strip_quotes($path);
  5168.             $nt_name = strip_quotes($nt_name) if $nt_name;
  5169.  
  5170.             # make sure they don't have broken regexps in the profile
  5171.             my $p_re = convert_regexp($path);
  5172.             eval { "foo" =~ m/^$p_re$/; };
  5173.             if ($@) {
  5174.                 die sprintf(gettext('Profile %s contains invalid regexp %s.'),
  5175.                                      $file, $path) . "\n";
  5176.             }
  5177.  
  5178.             if (!validate_profile_mode($mode, $allow, $nt_name)) {
  5179.                 fatal_error(sprintf(gettext('Profile %s contains invalid mode %s.'), $file, $mode));
  5180.             }
  5181.  
  5182.         my $tmpmode;
  5183.         if ($user) {
  5184.         $tmpmode = str_to_mode("${mode}::");
  5185.         } else {
  5186.         $tmpmode = str_to_mode($mode);
  5187.         }
  5188.  
  5189.             $profile_data->{$profile}{$hat}{$allow}{path}{$path}{mode} |= $tmpmode;
  5190.             $profile_data->{$profile}{$hat}{$allow}{path}{$path}{to} = $nt_name if $nt_name;
  5191.         if ($audit) {
  5192.         $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} |= $tmpmode;
  5193.         } else {
  5194.         $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} |= 0;
  5195.         }
  5196.         } elsif (m/^\s*#include <(.+)>\s*$/) {     # include stuff
  5197.             my $include = $1;
  5198.  
  5199.             if ($profile) {
  5200.                 $profile_data->{$profile}{$hat}{include}{$include} = 1;
  5201.             } else {
  5202.                 unless (exists $filelist{$file}) {
  5203.                    $filelist{$file} = { };
  5204.                 }
  5205.                 $filelist{$file}{include}{$include} = 1;
  5206.             }
  5207.  
  5208.             # include is a dir
  5209.             if (-d "$profiledir/$include") {
  5210.                 if (opendir(SDINCDIR, "$profiledir/$include")) {
  5211.                     for my $path (readdir(SDINCDIR)) {
  5212.                         chomp($path);
  5213.                         next if isSkippableFile($path);
  5214.                         if (-f "$profiledir/$include/$path") {
  5215.                             my $file = "$include/$path";
  5216.                             $file =~ s/$profiledir\///;
  5217.                             my $ret = eval { loadinclude($file); };
  5218.                             if ($@) { die $@; }
  5219.                             return $ret if ( $ret != 0 );
  5220.                         }
  5221.                     }
  5222.                 }
  5223.                 closedir(SDINCDIR);
  5224.             } else {
  5225.                 # try to load the include...
  5226.                 my $ret = eval { loadinclude($include); };
  5227.                 # propagate errors up the chain
  5228.                 if ($@) { die $@; }
  5229.                 return $ret if ( $ret != 0 );
  5230.             }
  5231.         } elsif (/^\s*(audit\s+)?(deny\s+)?network(.*)/) {
  5232.             if (not $profile) {
  5233.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5234.             }
  5235.         my $audit = $1 ? 1 : 0;
  5236.         my $allow = $2 ? 'deny' : 'allow';
  5237.         my $network = $3;
  5238.  
  5239.             unless ($profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}) {
  5240.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule} = { };
  5241.             }
  5242.  
  5243.             if ($network =~ /\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$/ ) {
  5244.         my $fam = $1;
  5245.         my $type = $2;
  5246.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam}{$type} = 1;
  5247.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam}{$type} = $audit;
  5248.             } elsif ( $network =~ /\s+(\S+)\s*,\s*(#.*)?$/ ) {
  5249.         my $fam = $1;
  5250.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam} = 1;
  5251.         $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam} = $audit;
  5252.             } else {
  5253.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{all} = 1;
  5254.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{all} = 1;
  5255.             }
  5256.         } elsif (/^\s*(tcp_connect|tcp_accept|udp_send|udp_receive)/) {
  5257. # just ignore and drop old style network
  5258. #        die sprintf(gettext('%s contains old style network rules.'), $file) . "\n";
  5259.  
  5260.         } elsif (m/^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$/) {
  5261.         if (not $profile) {
  5262.         die "$file contains syntax errors.";
  5263.         }
  5264.         # change_hat declaration - needed to change_hat to an external
  5265.         # hat
  5266.             $hat = $1;
  5267.             $hat = $1 if $hat =~ /^"(.+)"$/;
  5268.  
  5269.         #store we have a declaration if the hat hasn't been seen
  5270.         $profile_data->{$profile}{$hat}{'declared'} = 1
  5271.         unless exists($profile_data->{$profile}{$hat}{declared});
  5272.  
  5273.         } elsif (m/^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/) {
  5274.             # start of embedded hat syntax hat definition
  5275.             # read in and mark as changed so that will be written out in the new
  5276.             # format
  5277.  
  5278.             # if we hit the start of a contained hat when we're not in a profile
  5279.             # something is wrong...
  5280.             if (not $profile) {
  5281.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5282.             }
  5283.  
  5284.             $in_contained_hat = 1;
  5285.  
  5286.             # we hit the start of a hat inside the current profile
  5287.             $hat = $1;
  5288.             my $flags = $4;
  5289.  
  5290.             # strip quotes.
  5291.             $hat = $1 if $hat =~ /^"(.+)"$/;
  5292.  
  5293.             # keep track of profile flags
  5294.         $profile_data->{$profile}{$hat}{flags} = $flags;
  5295.         # we have seen more than a declaration so clear it
  5296.         $profile_data->{$profile}{$hat}{'declared'} = 0;
  5297.             $profile_data->{$profile}{$hat}{allow}{path} = { };
  5298.             $profile_data->{$profile}{$hat}{allow}{netdomain} = { };
  5299.  
  5300.             # store off initial comment if they have one
  5301.             $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment
  5302.               if $initial_comment;
  5303.             $initial_comment = "";
  5304.             #don't mark profile as changed just because it has an embedded
  5305.         #hat.
  5306.             #$changed{$profile} = 1;
  5307.  
  5308.         $filelist{$file}{profiles}{$profile}{$hat} = 1;
  5309.  
  5310.         } elsif (/^\s*\#/) {
  5311.             # we only currently handle initial comments
  5312.             if (not $profile) {
  5313.                 # ignore vim syntax highlighting lines
  5314.                 next if /^\s*\# vim:syntax/;
  5315.                 # ignore Last Modified: lines
  5316.                 next if /^\s*\# Last Modified:/;
  5317.                 if (/^\s*\# REPOSITORY: (\S+) (\S+) (\S+)$/) {
  5318.                     $repo_data = { url => $1, user => $2, id => $3 };
  5319.                 } elsif (/^\s*\# REPOSITORY: NEVERSUBMIT$/) {
  5320.                     $repo_data = { neversubmit => 1 };
  5321.                 } else {
  5322.                   $initial_comment .= "$_\n";
  5323.                 }
  5324.             }
  5325.         } else {
  5326.         # we hit something we don't understand in a profile...
  5327.         die sprintf(gettext('%s contains syntax errors. Line [%s]'), $file, $_) . "\n";
  5328.         }
  5329.     }
  5330.  
  5331.     #
  5332.     # Cleanup : add required hats if not present in the
  5333.     #           parsed profiles
  5334.     #
  5335. if (not $do_include) {
  5336.     for my $hatglob (keys %{$cfg->{required_hats}}) {
  5337.         for my $parsed_profile  ( sort @parsed_profiles )  {
  5338.             if ($parsed_profile =~ /$hatglob/) {
  5339.                 for my $hat (split(/\s+/, $cfg->{required_hats}{$hatglob})) {
  5340.                     unless ($profile_data->{$parsed_profile}{$hat}) {
  5341.                         $profile_data->{$parsed_profile}{$hat} = { };
  5342.                     }
  5343.                 }
  5344.             }
  5345.         }
  5346.     }
  5347.  
  5348. }    # if we're still in a profile when we hit the end of the file, it's bad
  5349.     if ($profile and not $do_include) {
  5350.         die "Reached the end of $file while we were still inside the $profile profile.\n";
  5351.     }
  5352.  
  5353.     return $profile_data;
  5354. }
  5355.  
  5356. sub eliminate_duplicates(@) {
  5357.     my @data =@_;
  5358.  
  5359.     my %set = map { $_ => 1 } @_;
  5360.     @data = keys %set;
  5361.  
  5362.     return @data;
  5363. }
  5364.  
  5365. sub separate_vars($) {
  5366.     my $vs = shift;
  5367.     my @data;
  5368.  
  5369. #    while ($vs =~ /\s*(((\"([^\"]|\\\"))+?\")|\S*)\s*(.*)$/) {
  5370.     while ($vs =~ /\s*((\".+?\")|([^\"]\S+))\s*(.*)$/) {
  5371.     my $tmp = $1;
  5372.     push @data, strip_quotes($tmp);
  5373.     $vs = $4;
  5374.     }
  5375.  
  5376.     return @data;
  5377. }
  5378.  
  5379. sub is_active_profile ($) {
  5380.     my $pname = shift;
  5381.     if ( $sd{$pname} ) {
  5382.         return 1;
  5383.     }  else {
  5384.         return 0;
  5385.     }
  5386. }
  5387.  
  5388. sub store_list_var (\%$$) {
  5389.     my ($vars, $list_var, $value) = @_;
  5390.  
  5391.     my @vlist = (separate_vars($value));
  5392.  
  5393. #       if (exists $profile_data->{$profile}{$hat}{lvar}{$list_var}) {
  5394. #           @vlist = (@vlist, @{$profile_data->{$profile}{$hat}{lvar}{$list_var}});
  5395. #       }
  5396. #
  5397. #       @vlist = eliminate_duplicates(@vlist);
  5398. #       $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@vlist;
  5399.  
  5400.     if (exists $vars->{$list_var}) {
  5401.     @vlist = (@vlist, @{$vars->{$list_var}});
  5402.     }
  5403.  
  5404.     @vlist = eliminate_duplicates(@vlist);
  5405.     $vars->{$list_var} = \@vlist;
  5406.  
  5407.  
  5408. }
  5409.  
  5410. sub strip_quotes ($) {
  5411.     my $data = shift;
  5412.     $data = $1 if $data =~ /^\"(.*)\"$/;
  5413.     return $data;
  5414. }
  5415.  
  5416. sub quote_if_needed ($) {
  5417.     my $data = shift;
  5418.     $data = "\"$data\"" if $data =~ /\s/;
  5419.  
  5420.     return $data;
  5421. }
  5422.  
  5423. sub escape ($) {
  5424.     my $dangerous = shift;
  5425.  
  5426.     $dangerous = strip_quotes($dangerous);
  5427.  
  5428.     $dangerous =~ s/((?<!\\))"/$1\\"/g;
  5429.     if ($dangerous =~ m/(\s|^$|")/) {
  5430.         $dangerous = "\"$dangerous\"";
  5431.     }
  5432.  
  5433.     return $dangerous;
  5434. }
  5435.  
  5436. sub writeheader ($$$$$) {
  5437.     my ($profile_data, $depth, $name, $embedded_hat, $write_flags) = @_;
  5438.  
  5439.     my $pre = '  ' x $depth;
  5440.     my @data;
  5441.     # deal with whitespace in profile names...
  5442.     $name = quote_if_needed($name);
  5443.  
  5444.     $name = "profile $name" if ((!$embedded_hat && $name =~ /^[^\/]|^"[^\/]/)
  5445.                 || ($embedded_hat && $name =~/^[^^]/));
  5446.  
  5447.     #push @data, "#include <tunables/global>" unless ( $is_hat );
  5448.     if ($write_flags and  $profile_data->{flags}) {
  5449.         push @data, "${pre}$name flags=($profile_data->{flags}) {";
  5450.     } else {
  5451.         push @data, "${pre}$name {";
  5452.     }
  5453.  
  5454.     return @data;
  5455. }
  5456.  
  5457. sub qin_trans ($) {
  5458.     my $value = shift;
  5459.     return quote_if_needed($value);
  5460. }
  5461.  
  5462. sub write_single ($$$$$$) {
  5463.     my ($profile_data, $depth, $allow, $name, $prefix, $tail) = @_;
  5464.     my $ref;
  5465.     my @data;
  5466.  
  5467.     if ($allow) {
  5468.     $ref = $profile_data->{$allow};
  5469.     if ($allow eq 'deny') {
  5470.         $allow .= " ";
  5471.     } else {
  5472.         $allow = "";
  5473.     }
  5474.     } else {
  5475.     $ref = $profile_data;
  5476.     $allow = "";
  5477.     }
  5478.  
  5479.     my $pre = "  " x $depth;
  5480.  
  5481.  
  5482.     # dump out the data
  5483.     if (exists $ref->{$name}) {
  5484.         for my $key (sort keys %{$ref->{$name}}) {
  5485.         my $qkey = quote_if_needed($key);
  5486.         push @data, "${pre}${allow}${prefix}${qkey}${tail}";
  5487.         }
  5488.         push @data, "" if keys %{$ref->{$name}};
  5489.     }
  5490.  
  5491.     return @data;
  5492. }
  5493.  
  5494. sub write_pair ($$$$$$$$) {
  5495.     my ($profile_data, $depth, $allow, $name, $prefix, $sep, $tail, $fn) = @_;
  5496.     my $ref;
  5497.     my @data;
  5498.  
  5499.     if ($allow) {
  5500.     $ref = $profile_data->{$allow};
  5501.     if ($allow eq 'deny') {
  5502.         $allow .= " ";
  5503.     } else {
  5504.         $allow = "";
  5505.     }
  5506.     } else {
  5507.     $ref = $profile_data;
  5508.     $allow = "";
  5509.     }
  5510.  
  5511.     my $pre = "  " x $depth;
  5512.  
  5513.     # dump out the data
  5514.     if (exists $ref->{$name}) {
  5515.         for my $key (sort keys %{$ref->{$name}}) {
  5516.         my $value = &{$fn}($ref->{$name}{$key});
  5517.             push @data, "${pre}${allow}${prefix}${key}${sep}${value}${tail}";
  5518.         }
  5519.         push @data, "" if keys %{$ref->{$name}};
  5520.     }
  5521.  
  5522.     return @data;
  5523. }
  5524.  
  5525. sub writeincludes ($$) {
  5526.     my ($prof_data, $depth) = @_;
  5527.  
  5528.     return write_single($prof_data, $depth,'', 'include', "#include <", ">");
  5529. }
  5530.  
  5531. sub writechange_profile ($$) {
  5532.     my ($prof_data, $depth) = @_;
  5533.  
  5534.     return write_single($prof_data, $depth, '', 'change_profile', "change_profile -> ", ",");
  5535. }
  5536.  
  5537. sub writealiases ($$) {
  5538.     my ($prof_data, $depth) = @_;
  5539.  
  5540.     return write_pair($prof_data, $depth, '', 'alias', "alias ", " -> ", ",", \&qin_trans);
  5541. }
  5542.  
  5543. sub writerlimits ($$) {
  5544.     my ($prof_data, $depth) = @_;
  5545.  
  5546.     return write_pair($prof_data, $depth, '', 'rlimit', "set rlimit ", " <= ", ",", \&qin_trans);
  5547. }
  5548.  
  5549. # take a list references and process it
  5550. sub var_transform($) {
  5551.     my $ref = shift;
  5552.     my @in = @{$ref};
  5553.     my @data;
  5554.  
  5555.     foreach my $value (@in) {
  5556.     push @data, quote_if_needed($value);
  5557.     }
  5558.  
  5559.     return join " ", @data;
  5560. }
  5561.  
  5562. sub writelistvars ($$) {
  5563.     my ($prof_data, $depth) = @_;
  5564.  
  5565.     return write_pair($prof_data, $depth, '', 'lvar', "", " = ", ",", \&var_transform);
  5566. }
  5567.  
  5568. sub writecap_rules ($$$) {
  5569.     my ($profile_data, $depth, $allow) = @_;
  5570.  
  5571.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5572.     my $pre = "  " x $depth;
  5573.  
  5574.     my @data;
  5575.     if (exists $profile_data->{$allow}{capability}) {
  5576.         for my $cap (sort keys %{$profile_data->{$allow}{capability}}) {
  5577.         my $audit = ($profile_data->{$allow}{capability}{$cap}{audit}) ? 'audit ' : '';
  5578.         if ($profile_data->{$allow}{capability}{$cap}{set}) {
  5579.         push @data, "${pre}${audit}${allowstr}capability ${cap},";
  5580.         }
  5581.         }
  5582.     push @data, "";
  5583.     }
  5584.  
  5585.     return @data;
  5586. }
  5587.  
  5588. sub writecapabilities ($$) {
  5589.     my ($prof_data, $depth) = @_;
  5590.     my @data;
  5591.     push @data, write_single($prof_data, $depth, '', 'set_capability', "set capability ", ",");
  5592.     push @data, writecap_rules($prof_data, $depth, 'deny');
  5593.     push @data, writecap_rules($prof_data, $depth, 'allow');
  5594.     return @data;
  5595. }
  5596.  
  5597. sub writenet_rules ($$$) {
  5598.     my ($profile_data, $depth, $allow) = @_;
  5599.  
  5600.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5601.  
  5602.     my $pre = "  " x $depth;
  5603.     my $audit = "";
  5604.  
  5605.     my @data;
  5606.     # dump out the netdomain entries...
  5607.     if (exists $profile_data->{$allow}{netdomain}) {
  5608.         if ( $profile_data->{$allow}{netdomain}{rule} &&
  5609.              $profile_data->{$allow}{netdomain}{rule} eq 'all') {
  5610.         $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{all};
  5611.             push @data, "${pre}${audit}network,";
  5612.         } else {
  5613.             for my $fam (sort keys %{$profile_data->{$allow}{netdomain}{rule}}) {
  5614.                 if ( $profile_data->{$allow}{netdomain}{rule}{$fam} == 1 ) {
  5615.             $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam};
  5616.                     push @data, "${pre}${audit}${allowstr}network $fam,";
  5617.                 } else {
  5618.                     for my $type 
  5619.                         (sort keys %{$profile_data->{$allow}{netdomain}{rule}{$fam}}) {
  5620.                 $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam}{$type};
  5621.                 push @data, "${pre}${audit}${allowstr}network $fam $type,";
  5622.                     }
  5623.                 }
  5624.             }
  5625.         }
  5626.         push @data, "" if %{$profile_data->{$allow}{netdomain}};
  5627.     }
  5628.     return @data;
  5629.  
  5630. }
  5631.  
  5632. sub writenetdomain ($$) {
  5633.     my ($prof_data, $depth) = @_;
  5634.     my @data;
  5635.  
  5636.     push @data, writenet_rules($prof_data, $depth, 'deny');
  5637.     push @data, writenet_rules($prof_data, $depth, 'allow');
  5638.  
  5639.     return @data;
  5640. }
  5641.  
  5642. sub writelink_rules ($$$) {
  5643.     my ($profile_data, $depth, $allow) = @_;
  5644.  
  5645.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5646.     my $pre = "  " x $depth;
  5647.  
  5648.     my @data;
  5649.     if (exists $profile_data->{$allow}{link}) {
  5650.         for my $path (sort keys %{$profile_data->{$allow}{link}}) {
  5651.             my $to = $profile_data->{$allow}{link}{$path}{to};
  5652.         my $subset = ($profile_data->{$allow}{link}{$path}{mode} & $AA_LINK_SUBSET) ? 'subset ' : '';
  5653.         my $audit = ($profile_data->{$allow}{link}{$path}{audit}) ? 'audit ' : '';
  5654.             # deal with whitespace in path names
  5655.             $path = quote_if_needed($path);
  5656.         $to = quote_if_needed($to);
  5657.         push @data, "${pre}${audit}${allowstr}link ${subset}${path} -> ${to},";
  5658.         }
  5659.     push @data, "";
  5660.     }
  5661.  
  5662.     return @data;
  5663. }
  5664.  
  5665. sub writelinks ($$) {
  5666.     my ($profile_data, $depth) = @_;
  5667.     my @data;
  5668.  
  5669.     push @data, writelink_rules($profile_data, $depth, 'deny');
  5670.     push @data, writelink_rules($profile_data, $depth, 'allow');
  5671.  
  5672.     return @data;
  5673. }
  5674.  
  5675. sub writepath_rules ($$$) {
  5676.     my ($profile_data, $depth, $allow) = @_;
  5677.  
  5678.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5679.     my $pre = "  " x $depth;
  5680.  
  5681.     my @data;
  5682.     if (exists $profile_data->{$allow}{path}) {
  5683.         for my $path (sort keys %{$profile_data->{$allow}{path}}) {
  5684.             my $mode = $profile_data->{$allow}{path}{$path}{mode};
  5685.             my $audit = $profile_data->{$allow}{path}{$path}{audit};
  5686.         my $tail = "";
  5687.         $tail = " -> " . $profile_data->{$allow}{path}{$path}{to} if ($profile_data->{$allow}{path}{$path}{to});
  5688.         my ($user, $other) = split_mode($mode);
  5689.         my ($user_audit, $other_audit) = split_mode($audit);
  5690.         # determine whether the rule contains any owner only components
  5691.  
  5692.         while ($user || $other) {
  5693.         my $ownerstr = "";
  5694.         my ($tmpmode, $tmpaudit) = 0;
  5695.         if ($user & ~$other) {
  5696.             # user contains bits not set in other
  5697.             $ownerstr = "owner ";
  5698.             $tmpmode = $user & ~$other;
  5699.             $tmpaudit = $user_audit;
  5700.             $user &= ~$tmpmode;
  5701. #        } elsif ($other & ~$user) {
  5702. #            $ownerstr = "other ";
  5703. #            $tmpmode = $other & ~$user;
  5704. #            $tmpaudit = $other_audit;
  5705. #            $other &= ~$tmpmode;
  5706.         } else {
  5707.             if ($user_audit & ~$other_audit & $user) {
  5708.             $ownerstr = "owner ";
  5709.             $tmpaudit = $user_audit & ~$other_audit & $user;
  5710.             $tmpmode = $user & $tmpaudit;
  5711.             $user &= ~$tmpmode;
  5712. #            } elsif ($other_audit & ~$user_audit & $other) {
  5713. #            $ownerstr = "other ";
  5714. #            $tmpaudit = $other_audit & ~$user_audit & $other;
  5715. #            $tmpmode = $other & $tmpaudit;
  5716. #            $other &= ~$tmpmode;
  5717.             } else {
  5718.             # user == other && user_audit == other_audit
  5719.             $ownerstr = "";
  5720. #include exclusive other for now
  5721. #            $tmpmode = $user;
  5722. #            $tmpaudit = $user_audit;
  5723.             $tmpmode = $user | $other;
  5724.             $tmpaudit = $user_audit | $other_audit;
  5725.             $user &= ~$tmpmode;
  5726.             $other &= ~$tmpmode;
  5727.             }
  5728.         }
  5729.  
  5730.         if ($tmpmode & $tmpaudit) {
  5731.             my $modestr = mode_to_str($tmpmode & $tmpaudit);
  5732.             if ($path =~ /\s/) {
  5733.             push @data, "${pre}audit ${allowstr}${ownerstr}\"$path\" ${modestr}${tail},";
  5734.             } else {
  5735.             push @data, "${pre}audit ${allowstr}${ownerstr}$path ${modestr}${tail},";
  5736.             }
  5737.             $tmpmode &= ~$tmpaudit;
  5738.         }
  5739.         if ($tmpmode) {
  5740.             my $modestr = mode_to_str($tmpmode);
  5741.             if ($path =~ /\s/) {
  5742.             push @data, "${pre}${allowstr}${ownerstr}\"$path\" ${modestr}${tail},";
  5743.             } else {
  5744.             push @data, "${pre}${allowstr}${ownerstr}$path ${modestr}${tail},";
  5745.             }
  5746.         }
  5747.         }
  5748.  
  5749.         }
  5750.     push @data, "";
  5751.     }
  5752.  
  5753.     return @data;
  5754. }
  5755.  
  5756. sub writepaths ($$) {
  5757.     my ($prof_data, $depth) = @_;
  5758.  
  5759.     my @data;
  5760.     push @data, writepath_rules($prof_data, $depth, 'deny');
  5761.     push @data, writepath_rules($prof_data, $depth, 'allow');
  5762.  
  5763.     return @data;
  5764. }
  5765.  
  5766. sub write_rules ($$) {
  5767.     my ($prof_data, $depth) = @_;
  5768.  
  5769.     my @data;
  5770.     push @data, writealiases($prof_data, $depth);
  5771.     push @data, writelistvars($prof_data, $depth);
  5772.     push @data, writeincludes($prof_data, $depth);
  5773.     push @data, writerlimits($prof_data, $depth);
  5774.     push @data, writecapabilities($prof_data, $depth);
  5775.     push @data, writenetdomain($prof_data, $depth);
  5776.     push @data, writelinks($prof_data, $depth);
  5777.     push @data, writepaths($prof_data, $depth);
  5778.     push @data, writechange_profile($prof_data, $depth);
  5779.  
  5780.     return @data;
  5781. }
  5782.  
  5783. sub writepiece ($$$$$);
  5784. sub writepiece ($$$$$) {
  5785.     my ($profile_data, $depth, $name, $nhat, $write_flags) = @_;
  5786.  
  5787.     my $pre = '  ' x $depth;
  5788.     my @data;
  5789.     my $wname;
  5790.     my $inhat = 0;
  5791.     if ($name eq $nhat) {
  5792.     $wname = $name;
  5793.     } else {
  5794.     $wname = "$name//$nhat";
  5795.     $name = $nhat;
  5796.     $inhat = 1;
  5797.     }
  5798.     push @data, writeheader($profile_data->{$name}, $depth, $wname, 0, $write_flags);
  5799.     push @data, write_rules($profile_data->{$name}, $depth + 1);
  5800.  
  5801.     my $pre2 = '  ' x ($depth + 1);
  5802.     # write external hat declarations
  5803.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5804.     if ($profile_data->{$hat}{declared}) {
  5805.         push @data, "${pre2}^$hat,";
  5806.     }
  5807.     }
  5808.  
  5809.     if (!$inhat) {
  5810.     # write embedded hats
  5811.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5812.         if ((not $profile_data->{$hat}{external}) and
  5813.         (not $profile_data->{$hat}{declared})) {
  5814.         push @data, "";
  5815.         if ($profile_data->{$hat}{profile}) {
  5816.             push @data, map { "$_" } writeheader($profile_data->{$hat},
  5817.                              $depth + 1, $hat,
  5818.                              1, $write_flags);
  5819.         } else {
  5820.             push @data, map { "$_" } writeheader($profile_data->{$hat},
  5821.                              $depth + 1, "^$hat",
  5822.                              1, $write_flags);
  5823.         }
  5824.         push @data, map { "$_" } write_rules($profile_data->{$hat},
  5825.                              $depth + 2);
  5826.         push @data, "${pre2}}";
  5827.         }
  5828.     }
  5829.     push @data, "${pre}}";
  5830.  
  5831.     #write external hats
  5832.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5833.         if (($name eq $nhat) and $profile_data->{$hat}{external}) {
  5834.         push @data, "";
  5835.         push @data, map { "  $_" } writepiece($profile_data, $depth - 1,
  5836.                               $name, $hat, $write_flags);
  5837.         push @data, "  }";
  5838.         }
  5839.     }
  5840.     }
  5841.     return @data;
  5842. }
  5843.  
  5844. sub serialize_profile {
  5845.     my ($profile_data, $name, $options) = @_;
  5846.  
  5847.     my $string = "";
  5848.     my $include_metadata = 0;  # By default don't write out metadata
  5849.     my $include_flags = 1;
  5850.     if ( $options and ref($options) eq "HASH" ) {
  5851.        $include_metadata = 1 if ( defined $options->{METADATA} );
  5852.        $include_flags    = 0 if ( defined $options->{NO_FLAGS} );
  5853.     }
  5854.  
  5855.     if ($include_metadata) {
  5856.         # keep track of when the file was last updated
  5857.         $string .= "# Last Modified: " . localtime(time) . "\n";
  5858.  
  5859.         # print out repository metadata
  5860.         if ($profile_data->{$name}{repo}       &&
  5861.             $profile_data->{$name}{repo}{url}  &&
  5862.             $profile_data->{$name}{repo}{user} &&
  5863.             $profile_data->{$name}{repo}{id}) {
  5864.             my $repo = $profile_data->{$name}{repo};
  5865.             $string .= "# REPOSITORY: $repo->{url} $repo->{user} $repo->{id}\n";
  5866.         } elsif ($profile_data->{$name}{repo}{neversubmit}) {
  5867.             $string .= "# REPOSITORY: NEVERSUBMIT\n";
  5868.         }
  5869.     }
  5870.  
  5871.     # print out initial comment
  5872.     if ($profile_data->{$name}{initial_comment}) {
  5873.         my $comment = $profile_data->{$name}{initial_comment};
  5874.         $comment =~ s/\\n/\n/g;
  5875.         $string .= "$comment\n";
  5876.     }
  5877.  
  5878.     #bleah this is stupid the data structure needs to be reworked
  5879.     my $filename = getprofilefilename($name);
  5880.     my @data;
  5881.     if ($filelist{$filename}) {
  5882.     push @data, writealiases($filelist{$filename}, 0);
  5883.     push @data, writelistvars($filelist{$filename}, 0);
  5884.     push @data, writeincludes($filelist{$filename}, 0);
  5885.     }
  5886.  
  5887.  
  5888. # XXX - FIXME
  5889. #
  5890. #  # dump variables defined in this file
  5891. #  if ($variables{$filename}) {
  5892. #    for my $var (sort keys %{$variables{$filename}}) {
  5893. #      if ($var =~ m/^@/) {
  5894. #        my @values = sort @{$variables{$filename}{$var}};
  5895. #        @values = map { escape($_) } @values;
  5896. #        my $values = join (" ", @values);
  5897. #        print SDPROF "$var = ";
  5898. #        print SDPROF $values;
  5899. #      } elsif ($var =~ m/^\$/) {
  5900. #        print SDPROF "$var = ";
  5901. #        print SDPROF ${$variables{$filename}{$var}};
  5902. #      } elsif ($var =~ m/^\#/) {
  5903. #        my $inc = $var;
  5904. #        $inc =~ s/^\#//;
  5905. #        print SDPROF "#include <$inc>";
  5906. #      }
  5907. #      print SDPROF "\n";
  5908. #    }
  5909. #  }
  5910.  
  5911.     push @data, writepiece($profile_data, 0, $name, $name, $include_flags);
  5912.     $string .= join("\n", @data);
  5913.  
  5914.     return "$string\n";
  5915. }
  5916.  
  5917. sub writeprofile_ui_feedback ($) {
  5918.     my $profile = shift;
  5919.     UI_Info(sprintf(gettext('Writing updated profile for %s.'), $profile));
  5920.     writeprofile($profile);
  5921. }
  5922.  
  5923. sub writeprofile ($) {
  5924.     my ($profile) = shift;
  5925.  
  5926.     my $filename = $sd{$profile}{$profile}{filename} || getprofilefilename($profile);
  5927.  
  5928.     open(SDPROF, ">$filename") or
  5929.       fatal_error "Can't write new AppArmor profile $filename: $!";
  5930.     my $serialize_opts = { };
  5931.     $serialize_opts->{METADATA} = 1;
  5932.  
  5933.     #make sure to write out all the profiles in the file
  5934.     my $profile_string = serialize_profile($sd{$profile}, $profile, $serialize_opts);
  5935.     print SDPROF $profile_string;
  5936.     close(SDPROF);
  5937.  
  5938.     # mark the profile as up-to-date
  5939.     delete $changed{$profile};
  5940.     $original_sd{$profile} = dclone($sd{$profile});
  5941. }
  5942.  
  5943. sub getprofileflags {
  5944.     my $filename = shift;
  5945.  
  5946.     my $flags = "enforce";
  5947.  
  5948.     if (open(PROFILE, "$filename")) {
  5949.         while (<PROFILE>) {
  5950.             if (m/^\s*\/\S+\s+flags=\((.+)\)\s+{\s*$/) {
  5951.                 $flags = $1;
  5952.                 close(PROFILE);
  5953.                 return $flags;
  5954.             }
  5955.         }
  5956.         close(PROFILE);
  5957.     }
  5958.  
  5959.     return $flags;
  5960. }
  5961.  
  5962.  
  5963. sub matchliteral {
  5964.     my ($sd_regexp, $literal) = @_;
  5965.  
  5966.     my $p_regexp = convert_regexp($sd_regexp);
  5967.  
  5968.     # check the log entry against our converted regexp...
  5969.     my $matches = eval { $literal =~ /^$p_regexp$/; };
  5970.  
  5971.     # doesn't match if we've got a broken regexp
  5972.     return undef if $@;
  5973.  
  5974.     return $matches;
  5975. }
  5976.  
  5977. # test if profile has exec rule for $exec_target
  5978. sub profile_known_exec (\%$$) {
  5979.     my ($profile, $type, $exec_target) = @_;
  5980.     if ( $type eq "exec" ) {
  5981.         my ($cm, $am, @m);
  5982.  
  5983.         # test denies first
  5984.         ($cm, $am, @m) = rematchfrag($profile, 'deny', $exec_target);
  5985.     if ($cm & $AA_MAY_EXEC) {
  5986.         return -1;
  5987.     }
  5988.         ($cm, $am, @m) = match_prof_incs_to_path($profile, 'deny', $exec_target);
  5989.     if ($cm & $AA_MAY_EXEC) {
  5990.         return -1;
  5991.     }
  5992.  
  5993.     # now test the generally longer allow lists
  5994.         ($cm, $am, @m) = rematchfrag($profile, 'allow', $exec_target);
  5995.     if ($cm & $AA_MAY_EXEC) {
  5996.         return 1;
  5997.     }
  5998.  
  5999.         ($cm, $am, @m) = match_prof_incs_to_path($profile, 'allow', $exec_target);
  6000.     if ($cm & $AA_MAY_EXEC) {
  6001.         return 1;
  6002.     }
  6003.     }
  6004.     return 0;
  6005. }
  6006.  
  6007. sub profile_known_capability (\%$) {
  6008.     my ($profile, $capname) = @_;
  6009.  
  6010.     return -1 if $profile->{deny}{capability}{$capname}{set};
  6011.     return 1 if $profile->{allow}{capability}{$capname}{set};
  6012.     for my $incname ( keys %{$profile->{include}} ) {
  6013.     return -1 if $include{$incname}{$incname}{deny}{capability}{$capname}{set};
  6014.     return 1 if $include{$incname}{$incname}{allow}{capability}{$capname}{set};
  6015.     }
  6016.     return 0;
  6017. }
  6018.  
  6019. sub profile_known_network (\%$$) {
  6020.     my ($profile, $family, $sock_type) = @_;
  6021.  
  6022.     return -1 if netrules_access_check( $profile->{deny}{netdomain},
  6023.                                        $family, $sock_type);
  6024.     return 1 if netrules_access_check( $profile->{allow}{netdomain},
  6025.                                        $family, $sock_type);
  6026.  
  6027.     for my $incname ( keys %{$profile->{include}} ) {
  6028.         return -1 if netrules_access_check($include{$incname}{$incname}{deny}{netdomain},
  6029.                                         $family, $sock_type);
  6030.         return 1 if netrules_access_check($include{$incname}{$incname}{allow}{netdomain},
  6031.                       $family, $sock_type);
  6032.     }
  6033.  
  6034.     return 0;
  6035. }
  6036.  
  6037. sub netrules_access_check ($$$) {
  6038.     my ($netrules, $family, $sock_type) = @_;
  6039.     return 0 if ( not defined $netrules );
  6040.     my %netrules        = %$netrules;
  6041.     my $all_net         = defined $netrules{rule}{all};
  6042.     my $all_net_family  = defined $netrules{rule}{$family} && $netrules{rule}{$family} == 1;
  6043.     my $net_family_sock = defined $netrules{rule}{$family} &&
  6044.                           ref($netrules{rule}{$family}) eq "HASH" &&
  6045.                           defined $netrules{rule}{$family}{$sock_type};
  6046.  
  6047.     if ( $all_net || $all_net_family || $net_family_sock ) {
  6048.         return 1;
  6049.     } else {
  6050.       return 0;
  6051.     }
  6052. }
  6053.  
  6054. sub reload_base($) {
  6055.     my $bin = shift;
  6056.  
  6057.     # don't try to reload profile if AppArmor is not running
  6058.     return unless check_for_subdomain();
  6059.  
  6060.     my $filename = getprofilefilename($bin);
  6061.  
  6062.     system("/bin/cat '$filename' | $parser -I$profiledir -r >/dev/null 2>&1");
  6063. }
  6064.  
  6065. sub reload ($) {
  6066.     my $bin = shift;
  6067.  
  6068.     # don't reload the profile if the corresponding executable doesn't exist
  6069.     my $fqdbin = findexecutable($bin) or return;
  6070.  
  6071.     return reload_base($fqdbin);
  6072. }
  6073.  
  6074. sub read_include_from_file {
  6075.     my $which = shift;
  6076.  
  6077.     my $data;
  6078.     if (open(INCLUDE, "$profiledir/$which")) {
  6079.         local $/;
  6080.         $data = <INCLUDE>;
  6081.         close(INCLUDE);
  6082.     }
  6083.  
  6084.     return $data;
  6085. }
  6086.  
  6087. sub get_include_data {
  6088.     my $which = shift;
  6089.  
  6090.     my $data = read_include_from_file($which);
  6091.     unless($data) {
  6092.         fatal_error "Can't find include file $which: $!";
  6093.     }
  6094.     return $data;
  6095. }
  6096.  
  6097. sub loadinclude {
  6098.     my $which = shift;
  6099.  
  6100.     # don't bother loading it again if we already have
  6101.     return 0 if $include{$which}{$which};
  6102.  
  6103.     my @loadincludes = ($which);
  6104.     while (my $incfile = shift @loadincludes) {
  6105.  
  6106.         my $data = get_include_data($incfile);
  6107.     my $incdata = parse_profile_data($data, $incfile, 1);
  6108.     if ($incdata) {
  6109.                     attach_profile_data(\%include, $incdata);
  6110.     }
  6111.     }
  6112.     return 0;
  6113. }
  6114.  
  6115. sub rematchfrag ($$$) {
  6116.     my ($frag, $allow, $path) = @_;
  6117.  
  6118.     my $combinedmode = 0;
  6119.     my $combinedaudit = 0;
  6120.     my @matches;
  6121.  
  6122.     for my $entry (keys %{ $frag->{$allow}{path} }) {
  6123.  
  6124.         my $regexp = convert_regexp($entry);
  6125.  
  6126.         # check the log entry against our converted regexp...
  6127.         if ($path =~ /^$regexp$/) {
  6128.  
  6129.             # regexp matches, add it's mode to the list to check against
  6130.             $combinedmode |= $frag->{$allow}{path}{$entry}{mode};
  6131.             $combinedaudit |= $frag->{$allow}{path}{$entry}{audit};
  6132.             push @matches, $entry;
  6133.         }
  6134.     }
  6135.  
  6136.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6137. }
  6138.  
  6139. sub match_include_to_path ($$$) {
  6140.     my ($incname, $allow, $path) = @_;
  6141.  
  6142.     my $combinedmode = 0;
  6143.     my $combinedaudit = 0;
  6144.     my @matches;
  6145.  
  6146.     my @includelist = ( $incname );
  6147.     while (my $incfile = shift @includelist) {
  6148.         my $ret = eval { loadinclude($incfile); };
  6149.         if ($@) { fatal_error $@; }
  6150.         my ($cm, $am, @m) = rematchfrag($include{$incfile}{$incfile}, $allow, $path);
  6151.         if ($cm) {
  6152.             $combinedmode |= $cm;
  6153.         $combinedaudit |= $am;
  6154.             push @matches, @m;
  6155.         }
  6156.  
  6157.         # check if a literal version is in the current include fragment
  6158.         if ($include{$incfile}{$incfile}{$allow}{path}{$path}) {
  6159.             $combinedmode |= $include{$incfile}{$incfile}{$allow}{path}{$path}{mode};
  6160.             $combinedaudit |= $include{$incfile}{$incfile}{$allow}{path}{$path}{audit};
  6161.         }
  6162.  
  6163.         # if this fragment includes others, check them too
  6164.         if (keys %{ $include{$incfile}{$incfile}{include} }) {
  6165.             push @includelist, keys %{ $include{$incfile}{$incfile}{include} };
  6166.         }
  6167.     }
  6168.  
  6169.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6170. }
  6171.  
  6172. sub match_prof_incs_to_path ($$$) {
  6173.     my ($frag, $allow, $path) = @_;
  6174.  
  6175.     my $combinedmode = 0;
  6176.     my $combinedaudit = 0;
  6177.     my @matches;
  6178.  
  6179.     # scan the include fragments for this profile looking for matches
  6180.     my @includelist = keys %{ $frag->{include} };
  6181.     while (my $include = shift @includelist) {
  6182.     my ($cm, $am, @m) = match_include_to_path($include, $allow, $path);
  6183.         if ($cm) {
  6184.             $combinedmode |= $cm;
  6185.             $combinedaudit |= $am;
  6186.             push @matches, @m;
  6187.         }
  6188.     }
  6189.  
  6190.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6191. }
  6192.  
  6193. #find includes that match the path to suggest
  6194. sub suggest_incs_for_path {
  6195.     my ($incname, $path, $allow) = @_;
  6196.  
  6197.  
  6198.     my $combinedmode = 0;
  6199.     my $combinedaudit = 0;
  6200.     my @matches;
  6201.  
  6202.     # scan the include fragments looking for matches
  6203.     my @includelist = ($incname);
  6204.     while (my $include = shift @includelist) {
  6205.         my ($cm, $am, @m) = rematchfrag($include{$include}{$include}, 'allow', $path);
  6206.         if ($cm) {
  6207.             $combinedmode |= $cm;
  6208.             $combinedaudit |= $am;
  6209.             push @matches, @m;
  6210.         }
  6211.  
  6212.         # check if a literal version is in the current include fragment
  6213.         if ($include{$include}{$include}{allow}{path}{$path}) {
  6214.             $combinedmode |= $include{$include}{$include}{allow}{path}{$path}{mode};
  6215.             $combinedaudit |= $include{$include}{$include}{allow}{path}{$path}{audit};
  6216.         }
  6217.  
  6218.         # if this fragment includes others, check them too
  6219.         if (keys %{ $include{$include}{$include}{include} }) {
  6220.             push @includelist, keys %{ $include{$include}{$include}{include} };
  6221.         }
  6222.     }
  6223.  
  6224.     if ($combinedmode) {
  6225.         return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6226.     } else {
  6227.         return;
  6228.     }
  6229. }
  6230.  
  6231. sub check_qualifiers {
  6232.     my $program = shift;
  6233.  
  6234.     if ($cfg->{qualifiers}{$program}) {
  6235.         unless($cfg->{qualifiers}{$program} =~ /p/) {
  6236.             fatal_error(sprintf(gettext("\%s is currently marked as a program that should not have it's own profile.  Usually, programs are marked this way if creating a profile for them is likely to break the rest of the system.  If you know what you're doing and are certain you want to create a profile for this program, edit the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf."), $program));
  6237.         }
  6238.     }
  6239. }
  6240.  
  6241. sub loadincludes {
  6242.     if (opendir(SDDIR, $profiledir)) {
  6243.         my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR);
  6244.         close(SDDIR);
  6245.  
  6246.         while (my $id = shift @incdirs) {
  6247.             next if isSkippableDir($id);
  6248.             if (opendir(SDDIR, "$profiledir/$id")) {
  6249.                 for my $path (readdir(SDDIR)) {
  6250.                     chomp($path);
  6251.                     next if isSkippableFile($path);
  6252.                     if (-f "$profiledir/$id/$path") {
  6253.                         my $file = "$id/$path";
  6254.                         $file =~ s/$profiledir\///;
  6255.                         my $ret = eval { loadinclude($file); };
  6256.                         if ($@) { fatal_error $@; }
  6257.                     } elsif (-d "$id/$path") {
  6258.                         push @incdirs, "$id/$path";
  6259.                     }
  6260.                 }
  6261.                 closedir(SDDIR);
  6262.             }
  6263.         }
  6264.     }
  6265. }
  6266.  
  6267. sub globcommon ($) {
  6268.     my $path = shift;
  6269.  
  6270.     my @globs;
  6271.  
  6272.     # glob library versions in both foo-5.6.so and baz.so.9.2 form
  6273.     if ($path =~ m/[\d\.]+\.so$/ || $path =~ m/\.so\.[\d\.]+$/) {
  6274.         my $libpath = $path;
  6275.         $libpath =~ s/[\d\.]+\.so$/*.so/;
  6276.         $libpath =~ s/\.so\.[\d\.]+$/.so.*/;
  6277.         push @globs, $libpath if $libpath ne $path;
  6278.     }
  6279.  
  6280.     for my $glob (keys %{$cfg->{globs}}) {
  6281.         if ($path =~ /$glob/) {
  6282.             my $globbedpath = $path;
  6283.             $globbedpath =~ s/$glob/$cfg->{globs}{$glob}/g;
  6284.             push @globs, $globbedpath if $globbedpath ne $path;
  6285.         }
  6286.     }
  6287.  
  6288.     if (wantarray) {
  6289.         return sort { length($b) <=> length($a) } uniq(@globs);
  6290.     } else {
  6291.         my @list = sort { length($b) <=> length($a) } uniq(@globs);
  6292.         return $list[$#list];
  6293.     }
  6294. }
  6295.  
  6296. # this is an ugly, nasty function that attempts to see if one regexp
  6297. # is a subset of another regexp
  6298. sub matchregexp ($$) {
  6299.     my ($new, $old) = @_;
  6300.  
  6301.     # bail out if old pattern has {foo,bar,baz} stuff in it
  6302.     return undef if $old =~ /\{.*(\,.*)*\}/;
  6303.  
  6304.     # are there any regexps at all in the old pattern?
  6305.     if ($old =~ /\[.+\]/ or $old =~ /\*/ or $old =~ /\?/) {
  6306.  
  6307.         # convert {foo,baz} to (foo|baz)
  6308.         $new =~ y/\{\}\,/\(\)\|/ if $new =~ /\{.*\,.*\}/;
  6309.  
  6310.         # \001 == SD_GLOB_RECURSIVE
  6311.         # \002 == SD_GLOB_SIBLING
  6312.  
  6313.         $new =~ s/\*\*/\001/g;
  6314.         $new =~ s/\*/\002/g;
  6315.  
  6316.         $old =~ s/\*\*/\001/g;
  6317.         $old =~ s/\*/\002/g;
  6318.  
  6319.         # strip common prefix
  6320.         my $prefix = commonprefix($new, $old);
  6321.         if ($prefix) {
  6322.  
  6323.             # make sure we don't accidentally gobble up a trailing * or **
  6324.             $prefix =~ s/(\001|\002)$//;
  6325.             $new    =~ s/^$prefix//;
  6326.             $old    =~ s/^$prefix//;
  6327.         }
  6328.  
  6329.         # strip common suffix
  6330.         my $suffix = commonsuffix($new, $old);
  6331.         if ($suffix) {
  6332.  
  6333.             # make sure we don't accidentally gobble up a leading * or **
  6334.             $suffix =~ s/^(\001|\002)//;
  6335.             $new    =~ s/$suffix$//;
  6336.             $old    =~ s/$suffix$//;
  6337.         }
  6338.  
  6339.         # if we boiled the differences down to a ** in the new entry, it matches
  6340.         # whatever's in the old entry
  6341.         return 1 if $new eq "\001";
  6342.  
  6343.         # if we've paired things down to a * in new, old matches if there are no
  6344.         # slashes left in the path
  6345.         return 1 if ($new eq "\002" && $old =~ /^[^\/]+$/);
  6346.  
  6347.         # we'll bail out if we have more globs in the old version
  6348.         return undef if $old =~ /\001|\002/;
  6349.  
  6350.         # see if we can match * globs in new against literal elements in old
  6351.         $new =~ s/\002/[^\/]*/g;
  6352.  
  6353.         return 1 if $old =~ /^$new$/;
  6354.  
  6355.     } else {
  6356.  
  6357.         my $new_regexp = convert_regexp($new);
  6358.  
  6359.         # check the log entry against our converted regexp...
  6360.         return 1 if $old =~ /^$new_regexp$/;
  6361.  
  6362.     }
  6363.  
  6364.     return undef;
  6365. }
  6366.  
  6367. sub combine_name($$) { return ($_[0] eq $_[1]) ? $_[0] : "$_[0]^$_[1]"; }
  6368. sub split_name ($) { my ($p, $h) = split(/\^/, $_[0]); $h ||= $p; ($p, $h); }
  6369.  
  6370. ##########################
  6371. #
  6372. # prompt_user($headers, $functions, $default, $options, $selected);
  6373. #
  6374. # $headers:
  6375. #   a required arrayref made up of "key, value" pairs in the order you'd
  6376. #   like them displayed to user
  6377. #
  6378. # $functions:
  6379. #   a required arrayref of the different options to display at the bottom
  6380. #   of the prompt like "(A)llow", "(D)eny", and "Ba(c)on".  the character
  6381. #   contained by ( and ) will be used as the key to select the specified
  6382. #   option.
  6383. #
  6384. # $default:
  6385. #   a required character which is the default "key" to enter when they
  6386. #   just hit enter
  6387. #
  6388. # $options:
  6389. #   an optional arrayref of the choices like the glob suggestions to be
  6390. #   presented to the user
  6391. #
  6392. # $selected:
  6393. #   specifies which option is currently selected
  6394. #
  6395. # when prompt_user() is called without an $options list, it returns a
  6396. # single value which is the key for the specified "function".
  6397. #
  6398. # when prompt_user() is called with an $options list, it returns an array
  6399. # of two elements, the key for the specified function as well as which
  6400. # option was currently selected
  6401. #######################################################################
  6402.  
  6403. sub Text_PromptUser ($) {
  6404.     my $question = shift;
  6405.  
  6406.     my $title     = $question->{title};
  6407.     my $explanation = $question->{explanation};
  6408.  
  6409.     my @headers   = (@{ $question->{headers} });
  6410.     my @functions = (@{ $question->{functions} });
  6411.  
  6412.     my $default  = $question->{default};
  6413.     my $options  = $question->{options};
  6414.     my $selected = $question->{selected} || 0;
  6415.  
  6416.     my $helptext = $question->{helptext};
  6417.  
  6418.     push @functions, "CMD_HELP" if $helptext;
  6419.  
  6420.     my %keys;
  6421.     my @menu_items;
  6422.     for my $cmd (@functions) {
  6423.  
  6424.         # make sure we know about this particular command
  6425.         my $cmdmsg = "PromptUser: " . gettext("Unknown command") . " $cmd";
  6426.         fatal_error $cmdmsg unless $CMDS{$cmd};
  6427.  
  6428.         # grab the localized text to use for the menu for this command
  6429.         my $menutext = gettext($CMDS{$cmd});
  6430.  
  6431.         # figure out what the hotkey for this menu item is
  6432.         my $menumsg = "PromptUser: " .
  6433.                       gettext("Invalid hotkey in") .
  6434.                       " '$menutext'";
  6435.         $menutext =~ /\((\S)\)/ or fatal_error $menumsg;
  6436.  
  6437.         # we want case insensitive comparisons so we'll force things to
  6438.         # lowercase
  6439.         my $key = lc($1);
  6440.  
  6441.         # check if we're already using this hotkey for this prompt
  6442.         my $hotkeymsg = "PromptUser: " .
  6443.                         gettext("Duplicate hotkey for") .
  6444.                         " $cmd: $menutext";
  6445.         fatal_error $hotkeymsg if $keys{$key};
  6446.  
  6447.         # keep track of which command they're picking if they hit this hotkey
  6448.         $keys{$key} = $cmd;
  6449.  
  6450.         if ($default && $default eq $cmd) {
  6451.             $menutext = "[$menutext]";
  6452.         }
  6453.  
  6454.         push @menu_items, $menutext;
  6455.     }
  6456.  
  6457.     # figure out the key for the default option
  6458.     my $default_key;
  6459.     if ($default && $CMDS{$default}) {
  6460.         my $defaulttext = gettext($CMDS{$default});
  6461.  
  6462.         # figure out what the hotkey for this menu item is
  6463.         my $defmsg = "PromptUser: " .
  6464.                       gettext("Invalid hotkey in default item") .
  6465.                       " '$defaulttext'";
  6466.         $defaulttext =~ /\((\S)\)/ or fatal_error $defmsg;
  6467.  
  6468.         # we want case insensitive comparisons so we'll force things to
  6469.         # lowercase
  6470.         $default_key = lc($1);
  6471.  
  6472.         my $defkeymsg = "PromptUser: " .
  6473.                         gettext("Invalid default") .
  6474.                         " $default";
  6475.         fatal_error $defkeymsg unless $keys{$default_key};
  6476.     }
  6477.  
  6478.     my $widest = 0;
  6479.     my @poo    = @headers;
  6480.     while (my $header = shift @poo) {
  6481.         my $value = shift @poo;
  6482.         $widest = length($header) if length($header) > $widest;
  6483.     }
  6484.     $widest++;
  6485.  
  6486.     my $format = '%-' . $widest . "s \%s\n";
  6487.  
  6488.     my $function_regexp = '^(';
  6489.     $function_regexp .= join("|", keys %keys);
  6490.     $function_regexp .= '|\d' if $options;
  6491.     $function_regexp .= ')$';
  6492.  
  6493.     my $ans = "XXXINVALIDXXX";
  6494.     while ($ans !~ /$function_regexp/i) {
  6495.         # build up the prompt...
  6496.         my $prompt = "\n";
  6497.  
  6498.         $prompt .= "= $title =\n\n" if $title;
  6499.  
  6500.         if (@headers) {
  6501.             my @poo = @headers;
  6502.             while (my $header = shift @poo) {
  6503.                 my $value = shift @poo;
  6504.                 $prompt .= sprintf($format, "$header:", $value);
  6505.             }
  6506.             $prompt .= "\n";
  6507.         }
  6508.  
  6509.         if ($explanation) {
  6510.             $prompt .= "$explanation\n\n";
  6511.         }
  6512.  
  6513.         if ($options) {
  6514.             for (my $i = 0; $options->[$i]; $i++) {
  6515.                 my $f = ($selected == $i) ? ' [%d - %s]' : '  %d - %s ';
  6516.                 $prompt .= sprintf("$f\n", $i + 1, $options->[$i]);
  6517.             }
  6518.             $prompt .= "\n";
  6519.         }
  6520.         $prompt .= join(" / ", @menu_items);
  6521.         print "$prompt\n";
  6522.  
  6523.         # get their input...
  6524.         $ans = lc(getkey());
  6525.  
  6526.         if ($ans) {
  6527.             # handle escape sequences so you can up/down in the list
  6528.             if ($ans eq "up") {
  6529.  
  6530.                 if ($options && ($selected > 0)) {
  6531.                     $selected--;
  6532.                 }
  6533.                 $ans = "XXXINVALIDXXX";
  6534.  
  6535.             } elsif ($ans eq "down") {
  6536.  
  6537.                 if ($options && ($selected < (scalar(@$options) - 1))) {
  6538.                     $selected++;
  6539.                 }
  6540.                 $ans = "XXXINVALIDXXX";
  6541.  
  6542.             } elsif ($keys{$ans} && $keys{$ans} eq "CMD_HELP") {
  6543.  
  6544.                 print "\n$helptext\n";
  6545.                 $ans = "XXXINVALIDXXX";
  6546.  
  6547.             } elsif (ord($ans) == 10) {
  6548.  
  6549.                 # pick the default if they hit return...
  6550.                 $ans = $default_key;
  6551.  
  6552.             } elsif ($options && ($ans =~ /^\d$/)) {
  6553.  
  6554.                 # handle option poo
  6555.                 if ($ans > 0 && $ans <= scalar(@$options)) {
  6556.                     $selected = $ans - 1;
  6557.                 }
  6558.                 $ans = "XXXINVALIDXXX";
  6559.             }
  6560.         }
  6561.  
  6562.         if ($keys{$ans} && $keys{$ans} eq "CMD_HELP") {
  6563.             print "\n$helptext\n";
  6564.             $ans = "again";
  6565.         }
  6566.     }
  6567.  
  6568.     # pull our command back from our hotkey map
  6569.     $ans = $keys{$ans} if $keys{$ans};
  6570.     return ($ans, $selected);
  6571.  
  6572. }
  6573.  
  6574. # Parse event record into key-value pairs
  6575. sub parse_event($) {
  6576.     my %ev = ();
  6577.     my $msg = shift;
  6578.     chomp($msg);
  6579.     my $event = LibAppArmor::parse_record($msg);
  6580.     my ($rmask, $dmask);
  6581.  
  6582.     $DEBUGGING && debug("parse_event: $msg");
  6583.  
  6584.     $ev{'resource'}   = LibAppArmor::aa_log_record::swig_info_get($event);
  6585.     $ev{'active_hat'} = LibAppArmor::aa_log_record::swig_active_hat_get($event);
  6586.     $ev{'sdmode'}     = LibAppArmor::aa_log_record::swig_event_get($event);
  6587.     $ev{'time'}       = LibAppArmor::aa_log_record::swig_epoch_get($event);
  6588.     $ev{'operation'}  = LibAppArmor::aa_log_record::swig_operation_get($event);
  6589.     $ev{'profile'}    = LibAppArmor::aa_log_record::swig_profile_get($event);
  6590.     $ev{'name'}       = LibAppArmor::aa_log_record::swig_name_get($event);
  6591.     $ev{'name2'}      = LibAppArmor::aa_log_record::swig_name2_get($event);
  6592.     $ev{'attr'}       = LibAppArmor::aa_log_record::swig_attribute_get($event);
  6593.     $ev{'parent'}     = LibAppArmor::aa_log_record::swig_parent_get($event);
  6594.     $ev{'pid'}        = LibAppArmor::aa_log_record::swig_pid_get($event);
  6595.     $ev{'task'}        = LibAppArmor::aa_log_record::swig_task_get($event);
  6596.     $ev{'info'}        = LibAppArmor::aa_log_record::swig_info_get($event);
  6597.     $dmask = LibAppArmor::aa_log_record::swig_denied_mask_get($event);
  6598.     $rmask = LibAppArmor::aa_log_record::swig_requested_mask_get($event);
  6599.     $ev{'magic_token'}  =
  6600.        LibAppArmor::aa_log_record::swig_magic_token_get($event);
  6601.  
  6602.     # NetDomain
  6603.     if ( $ev{'operation'} && $ev{'operation'} =~ /socket/ ) {
  6604.         $ev{'family'}    =
  6605.             LibAppArmor::aa_log_record::swig_net_family_get($event);
  6606.         $ev{'protocol'}  =
  6607.             LibAppArmor::aa_log_record::swig_net_protocol_get($event);
  6608.         $ev{'sock_type'} =
  6609.             LibAppArmor::aa_log_record::swig_net_sock_type_get($event);
  6610.     }
  6611.  
  6612.     LibAppArmor::free_record($event);
  6613.  
  6614.     if ($rmask && !validate_log_mode(hide_log_mode($rmask))) {
  6615.         fatal_error(sprintf(gettext('Log contains unknown mode %s.'),
  6616.                             $rmask));
  6617.     }
  6618.  
  6619.     if ($dmask && !validate_log_mode(hide_log_mode($dmask))) {
  6620.         fatal_error(sprintf(gettext('Log contains unknown mode %s.'),
  6621.                     $dmask));
  6622.     }
  6623. #print "str_to_mode deny $dmask = " . str_to_mode($dmask) . "\n" if ($dmask);
  6624. #print "str_to_mode req $rmask = "  . str_to_mode($rmask) . "\n" if ($rmask);
  6625.  
  6626.     my ($mask, $name);
  6627.     ($mask, $name) = log_str_to_mode($ev{profile}, $dmask, $ev{name2});
  6628.     $ev{'denied_mask'} = $mask;
  6629.     $ev{name2} = $name;
  6630.  
  6631.     ($mask, $name) = log_str_to_mode($ev{profile}, $rmask, $ev{name2});
  6632.     $ev{'request_mask'} = $mask;
  6633.     $ev{name2} = $name;
  6634.  
  6635.     if ( ! $ev{'time'} ) { $ev{'time'} = time; }
  6636.  
  6637.     # remove null responses
  6638.     for (keys(%ev)) {
  6639.         if ( ! $ev{$_} || $ev{$_} !~ /[\/\w]+/)  { delete($ev{$_}); }
  6640.     }
  6641.  
  6642.     if ( $ev{'sdmode'} ) {
  6643.         #0 = invalid, 1 = error, 2 = AUDIT, 3 = ALLOW/PERMIT,
  6644.         #4 = DENIED/REJECTED, 5 = HINT, 6 = STATUS/config change
  6645.         if    ( $ev{'sdmode'} == 0 ) { $ev{'sdmode'} = "UNKNOWN"; }
  6646.         elsif ( $ev{'sdmode'} == 1 ) { $ev{'sdmode'} = "ERROR"; }
  6647.         elsif ( $ev{'sdmode'} == 2 ) { $ev{'sdmode'} = "AUDITING"; }
  6648.         elsif ( $ev{'sdmode'} == 3 ) { $ev{'sdmode'} = "PERMITTING"; }
  6649.         elsif ( $ev{'sdmode'} == 4 ) { $ev{'sdmode'} = "REJECTING"; }
  6650.         elsif ( $ev{'sdmode'} == 5 ) { $ev{'sdmode'} = "HINT"; }
  6651.         elsif ( $ev{'sdmode'} == 6 ) { $ev{'sdmode'} = "STATUS"; }
  6652.         else  { delete($ev{'sdmode'}); }
  6653.     }
  6654.     if ( $ev{sdmode} ) {
  6655.        $DEBUGGING && debug( Data::Dumper->Dump([%ev], [qw(*event)]));
  6656.        return \%ev;
  6657.     } else {
  6658.        return( undef );
  6659.     }
  6660. }
  6661.  
  6662. ###############################################################################
  6663. # required initialization
  6664.  
  6665. $cfg = read_config("logprof.conf");
  6666. if ((not defined $cfg->{settings}{default_owner_prompt})) {
  6667.     $cfg->{settings}{default_owner_prompt} = 0;
  6668. }
  6669.  
  6670. $profiledir = find_first_dir($cfg->{settings}{profiledir}) || "/etc/apparmor.d";
  6671. unless (-d $profiledir) { fatal_error "Can't find AppArmor profiles."; }
  6672.  
  6673. $extraprofiledir = find_first_dir($cfg->{settings}{inactive_profiledir}) ||
  6674. "/etc/apparmor/profiles/extras/";
  6675.  
  6676. $parser = find_first_file($cfg->{settings}{parser}) || "/sbin/apparmor_parser";
  6677. unless (-x $parser) { fatal_error "Can't find apparmor_parser."; }
  6678.  
  6679. $filename = find_first_file($cfg->{settings}{logfiles}) || "/var/log/messages";
  6680. unless (-f $filename) { fatal_error "Can't find system log."; }
  6681.  
  6682. $ldd = find_first_file($cfg->{settings}{ldd}) || "/usr/bin/ldd";
  6683. unless (-x $ldd) { fatal_error "Can't find ldd."; }
  6684.  
  6685. $logger = find_first_file($cfg->{settings}{logger}) || "/bin/logger";
  6686. unless (-x $logger) { fatal_error "Can't find logger."; }
  6687.  
  6688. 1;
  6689.  
  6690.